phy for 5.9

- New PHY Drivers:
    - Samsung UFS
    - Qcom USB DWC for ipq806x
    - Xilinx ZynqMP Gigabit Transceiver
    - Qcom USB QMP for IPQ8074
    - BCM63xx USBH
 
  - Removed:
    - Qcom ufs qmp phy driver
 
  - Updates:
    - Support for Qcom SM8250 QMP V4 USB3 UNIPHY
    - qcom-snps runtime pm support
    - Cleanup of W=1 warns in the subsystem
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEE+vs47OPLdNbVcHzyfBQHDyUjg0cFAl8ZOkIACgkQfBQHDyUj
 g0el3g//Q/e0qIg7WybLWhbB7tGO5lP/OFOVVwVB54w35VX8cCiCuuqeiaENJ81e
 x4Y8DUlKxTL0XkDwNs+yp6njU6kjZjFk1+xWnFBQIFczXWbLezNlwM2rkjwAI0oO
 A3PW0qbWuhgc7tRfQ9ULnrG6kHtvRohT/E/Qe6nUhYaz2LJ96pY5rBg8vzrQxoXn
 4vMs79dGqnxlhDASE5yKLYTus5/ntMRQCiblQjxnfRqGaNTgj40iejxzj7FEch/d
 H3x2/ylY9PTHXzIlrM1KdcBeKz0tfBZHOer7/kGhfmGGsoMNW8DUTEMgVzOBp+Yb
 VHyQBHWcVKvvv1q5XCNRN1GuGgOdNAblfmMwRQVxafNOP+tqKDdmy2LHFzA3MrDv
 XqxRXwmegTOJJ+Wl/pSXoRtergCXJhDNo5HDY03vlDj1AvYMxjHIjyay629ZD64C
 S0hxViw7Y+4W5y3DET69axRNB7ckgMuPniUf1MRSSTXLBSrL5jaCA9WvTcSeGI2y
 8kVmro5zybn3DqVMrd5q/LaHpMwYkK8Iy5BrZYJWmDYj3jsOYp96CVTc07//btOW
 vmjYU0h0YJupPtYZTOKBY7d9BEys3Xp4tUaiz9ymBOMSsXJKoD4u3OH1ePGV9/Zm
 5S7WEWb3jBjc6WdjSW2eHeMG/+gj3K5Rcg3JAcm3E8E4W8djKLE=
 =pkO8
 -----END PGP SIGNATURE-----

Merge tag 'phy-for-5.9' of git://git.kernel.org/pub/scm/linux/kernel/git/phy/linux-phy into char-misc-next

Vinod writes:

phy for 5.9

 - New PHY Drivers:
   - Samsung UFS
   - Qcom USB DWC for ipq806x
   - Xilinx ZynqMP Gigabit Transceiver
   - Qcom USB QMP for IPQ8074
   - BCM63xx USBH

 - Removed:
   - Qcom ufs qmp phy driver

 - Updates:
   - Support for Qcom SM8250 QMP V4 USB3 UNIPHY
   - qcom-snps runtime pm support
   - Cleanup of W=1 warns in the subsystem

* tag 'phy-for-5.9' of git://git.kernel.org/pub/scm/linux/kernel/git/phy/linux-phy: (46 commits)
  phy: qualcomm: fix setting of tx_deamp_3_5db when device property read fails
  phy: bcm63xx-usbh: Add BCM63xx USBH driver
  dt-bindings: phy: add bcm63xx-usbh bindings
  phy: armada-38x: fix NETA lockup when repeatedly switching speeds
  dt: update Marvell Armada 38x COMPHY binding
  phy: samsung-ufs: Fix IS_ERR argument
  dt-bindings: phy: renesas,usb3-phy: Add r8a774e1 support
  dt-bindings: phy: renesas,usb2-phy: Add r8a774e1 support
  phy: renesas: rcar-gen3-usb2: exit if request_irq() failed
  phy: renesas: rcar-gen3-usb2: move irq registration to init
  devicetree: bindings: phy: Document ipq806x dwc3 qcom phy
  phy: qualcomm: add qcom ipq806x dwc usb phy driver
  phy: samsung-ufs: add UFS PHY driver for samsung SoC
  dt-bindings: phy: Document Samsung UFS PHY bindings
  phy: sun4i-usb: explicitly include gpio/consumer.h
  phy: stm32: use NULL instead of zero
  phy: exynos5-usbdrd: use correct format for structure description
  phy: rockchip-typec: use correct format for structure description
  phy: xgene: remove unsigned integer comparison with less than zero
  phy: mapphone-mdm6600: Add missing description for some structure fields
  ...
This commit is contained in:
Greg Kroah-Hartman 2020-07-23 09:52:10 +02:00
commit 1859a772e2
58 changed files with 3789 additions and 1694 deletions

View File

@ -0,0 +1,79 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: "http://devicetree.org/schemas/phy/brcm,bcm63xx-usbh-phy.yaml#"
$schema: "http://devicetree.org/meta-schemas/core.yaml#"
title: BCM63xx USBH PHY
maintainers:
- Álvaro Fernández Rojas <noltari@gmail.com>
properties:
compatible:
enum:
- brcm,bcm6318-usbh-phy
- brcm,bcm6328-usbh-phy
- brcm,bcm6358-usbh-phy
- brcm,bcm6362-usbh-phy
- brcm,bcm6368-usbh-phy
- brcm,bcm63268-usbh-phy
reg:
maxItems: 1
clocks:
minItems: 1
maxItems: 2
clock-names:
minItems: 1
maxItems: 2
items:
- const: usbh
- const: usb_ref
resets:
maxItems: 1
"#phy-cells":
const: 1
additionalProperties: false
required:
- compatible
- reg
- clocks
- clock-names
- resets
- "#phy-cells"
if:
properties:
compatible:
enum:
- brcm,bcm6318-usbh-phy
- brcm,bcm6328-usbh-phy
- brcm,bcm6362-usbh-phy
- brcm,bcm63268-usbh-phy
then:
properties:
power-domains:
maxItems: 1
required:
- power-domains
else:
properties:
power-domains: false
examples:
- |
usbh: usb-phy@10001700 {
compatible = "brcm,bcm6368-usbh-phy";
reg = <0x10001700 0x38>;
clocks = <&periph_clk 15>;
clock-names = "usbh";
resets = <&periph_rst 12>;
#phy-cells = <1>;
};

View File

@ -12,6 +12,13 @@ Required properties:
- #address-cells: should be 1.
- #size-cells: should be 0.
Optional properties:
- reg-names: must be "comphy" as the first name, and "conf".
- reg: must contain the comphy register location and length as the first
pair, followed by an optional configuration register address and
length pair.
A sub-node is required for each comphy lane provided by the comphy.
Required properties (child nodes):
@ -24,7 +31,8 @@ Example:
comphy: phy@18300 {
compatible = "marvell,armada-380-comphy";
reg = <0x18300 0x100>;
reg-names = "comphy", "conf";
reg = <0x18300 0x100>, <0x18460 4>;
#address-cells = <1>;
#size-cells = <0>;

View File

@ -0,0 +1,55 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/phy/qcom,ipq806x-usb-phy-hs.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Qualcomm ipq806x usb DWC3 HS PHY CONTROLLER
maintainers:
- Ansuel Smith <ansuelsmth@gmail.com>
description:
DWC3 PHY nodes are defined to describe on-chip Synopsis Physical layer
controllers used in ipq806x. Each DWC3 PHY controller should have its
own node.
properties:
compatible:
const: qcom,ipq806x-usb-phy-hs
"#phy-cells":
const: 0
reg:
maxItems: 1
clocks:
minItems: 1
maxItems: 2
clock-names:
minItems: 1
maxItems: 2
items:
- const: ref
- const: xo
required:
- compatible
- "#phy-cells"
- reg
- clocks
- clock-names
examples:
- |
#include <dt-bindings/clock/qcom,gcc-ipq806x.h>
hs_phy_0: phy@110f8800 {
compatible = "qcom,ipq806x-usb-phy-hs";
reg = <0x110f8800 0x30>;
clocks = <&gcc USB30_0_UTMI_CLK>;
clock-names = "ref";
#phy-cells = <0>;
};

View File

@ -0,0 +1,73 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/phy/qcom,ipq806x-usb-phy-ss.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Qualcomm ipq806x usb DWC3 SS PHY CONTROLLER
maintainers:
- Ansuel Smith <ansuelsmth@gmail.com>
description:
DWC3 PHY nodes are defined to describe on-chip Synopsis Physical layer
controllers used in ipq806x. Each DWC3 PHY controller should have its
own node.
properties:
compatible:
const: qcom,ipq806x-usb-phy-ss
"#phy-cells":
const: 0
reg:
maxItems: 1
clocks:
minItems: 1
maxItems: 2
clock-names:
minItems: 1
maxItems: 2
items:
- const: ref
- const: xo
qcom,rx-eq:
$ref: /schemas/types.yaml#/definitions/uint32
description: Override value for rx_eq.
default: 4
maximum: 7
qcom,tx-deamp-3_5db:
$ref: /schemas/types.yaml#/definitions/uint32
description: Override value for transmit preemphasis.
default: 23
maximum: 63
qcom,mpll:
$ref: /schemas/types.yaml#/definitions/uint32
description: Override value for mpll.
default: 0
maximum: 7
required:
- compatible
- "#phy-cells"
- reg
- clocks
- clock-names
examples:
- |
#include <dt-bindings/clock/qcom,gcc-ipq806x.h>
ss_phy_0: phy@110f8830 {
compatible = "qcom,ipq806x-usb-phy-ss";
reg = <0x110f8830 0x30>;
clocks = <&gcc USB30_0_MASTER_CLK>;
clock-names = "ref";
#phy-cells = <0>;
};

View File

@ -18,6 +18,7 @@ properties:
compatible:
enum:
- qcom,ipq8074-qmp-pcie-phy
- qcom,ipq8074-qmp-usb3-phy
- qcom,msm8996-qmp-pcie-phy
- qcom,msm8996-qmp-ufs-phy
- qcom,msm8996-qmp-usb3-phy
@ -161,6 +162,7 @@ allOf:
compatible:
contains:
enum:
- qcom,ipq8074-qmp-usb3-phy
- qcom,msm8996-qmp-usb3-phy
- qcom,msm8998-qmp-pcie-phy
- qcom,msm8998-qmp-usb3-phy

View File

@ -18,6 +18,7 @@ properties:
oneOf:
- items:
- enum:
- qcom,ipq8074-qusb2-phy
- qcom,msm8996-qusb2-phy
- qcom,msm8998-qusb2-phy
- items:

View File

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

View File

@ -15,6 +15,7 @@ properties:
- enum:
- renesas,r8a774a1-usb3-phy # RZ/G2M
- renesas,r8a774b1-usb3-phy # RZ/G2N
- renesas,r8a774e1-usb3-phy # RZ/G2H
- renesas,r8a7795-usb3-phy # R-Car H3
- renesas,r8a7796-usb3-phy # R-Car M3-W
- renesas,r8a77961-usb3-phy # R-Car M3-W+

View File

@ -0,0 +1,75 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/phy/samsung,ufs-phy.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Samsung SoC series UFS PHY Device Tree Bindings
maintainers:
- Alim Akhtar <alim.akhtar@samsung.com>
properties:
"#phy-cells":
const: 0
compatible:
enum:
- samsung,exynos7-ufs-phy
reg:
maxItems: 1
reg-names:
items:
- const: phy-pma
clocks:
items:
- description: PLL reference clock
- description: symbol clock for input symbol ( rx0-ch0 symbol clock)
- description: symbol clock for input symbol ( rx1-ch1 symbol clock)
- description: symbol clock for output symbol ( tx0 symbol clock)
clock-names:
items:
- const: ref_clk
- const: rx1_symbol_clk
- const: rx0_symbol_clk
- const: tx0_symbol_clk
samsung,pmu-syscon:
$ref: '/schemas/types.yaml#/definitions/phandle'
description: phandle for PMU system controller interface, used to
control pmu registers bits for ufs m-phy
required:
- "#phy-cells"
- compatible
- reg
- reg-names
- clocks
- clock-names
- samsung,pmu-syscon
additionalProperties: false
examples:
- |
#include <dt-bindings/clock/exynos7-clk.h>
ufs_phy: ufs-phy@15571800 {
compatible = "samsung,exynos7-ufs-phy";
reg = <0x15571800 0x240>;
reg-names = "phy-pma";
samsung,pmu-syscon = <&pmu_system_controller>;
#phy-cells = <0>;
clocks = <&clock_fsys1 SCLK_COMBO_PHY_EMBEDDED_26M>,
<&clock_fsys1 PHYCLK_UFS20_RX1_SYMBOL_USER>,
<&clock_fsys1 PHYCLK_UFS20_RX0_SYMBOL_USER>,
<&clock_fsys1 PHYCLK_UFS20_TX0_SYMBOL_USER>;
clock-names = "ref_clk", "rx1_symbol_clk",
"rx0_symbol_clk", "tx0_symbol_clk";
};
...

View File

@ -31,12 +31,16 @@ properties:
clocks:
minItems: 1
maxItems: 2
maxItems: 3
clock-names:
oneOf:
- const: link # for PXs2
- items: # for PXs3
- items: # for PXs3 with phy-ext
- const: link
- const: phy
- const: phy-ext
- items: # for others
- const: link
- const: phy

View File

@ -0,0 +1,105 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/phy/xlnx,zynqmp-psgtr.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Xilinx ZynqMP Gigabit Transceiver PHY Device Tree Bindings
maintainers:
- Laurent Pinchart <laurent.pinchart@ideasonboard.com>
description: |
This binding describes the Xilinx ZynqMP Gigabit Transceiver (GTR) PHY. The
GTR provides four lanes and is used by USB, SATA, PCIE, Display port and
Ethernet SGMII controllers.
properties:
"#phy-cells":
const: 4
description: |
The cells contain the following arguments.
- description: The GTR lane
minimum: 0
maximum: 3
- description: The PHY type
enum:
- PHY_TYPE_DP
- PHY_TYPE_PCIE
- PHY_TYPE_SATA
- PHY_TYPE_SGMII
- PHY_TYPE_USB
- description: The PHY instance
minimum: 0
maximum: 1 # for DP, SATA or USB
maximum: 3 # for PCIE or SGMII
- description: The reference clock number
minimum: 0
maximum: 3
compatible:
enum:
- xlnx,zynqmp-psgtr-v1.1
- xlnx,zynqmp-psgtr
clocks:
minItems: 1
maxItems: 4
description: |
Clock for each PS_MGTREFCLK[0-3] reference clock input. Unconnected
inputs shall not have an entry.
clock-names:
minItems: 1
maxItems: 4
items:
pattern: "^ref[0-3]$"
reg:
items:
- description: SERDES registers block
- description: SIOU registers block
reg-names:
items:
- const: serdes
- const: siou
xlnx,tx-termination-fix:
description: |
Include this for fixing functional issue with the TX termination
resistance in GT, which can be out of spec for the XCZU9EG silicon
version.
type: boolean
required:
- "#phy-cells"
- compatible
- reg
- reg-names
if:
properties:
compatible:
const: xlnx,zynqmp-psgtr-v1.1
then:
properties:
xlnx,tx-termination-fix: false
additionalProperties: false
examples:
- |
phy: phy@fd400000 {
compatible = "xlnx,zynqmp-psgtr-v1.1";
reg = <0xfd400000 0x40000>,
<0xfd3d0000 0x1000>;
reg-names = "serdes", "siou";
clocks = <&refclks 3>, <&refclks 2>, <&refclks 0>;
clock-names = "ref1", "ref2", "ref3";
#phy-cells = <4>;
};
...

View File

@ -18862,6 +18862,15 @@ F: Documentation/devicetree/bindings/media/xilinx/
F: drivers/media/platform/xilinx/
F: include/uapi/linux/xilinx-v4l2-controls.h
XILINX ZYNQMP PSGTR PHY DRIVER
M: Anurag Kumar Vulisha <anurag.kumar.vulisha@xilinx.com>
M: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
L: linux-kernel@vger.kernel.org
S: Supported
T: git https://github.com/Xilinx/linux-xlnx.git
F: Documentation/devicetree/bindings/phy/xlnx,zynqmp-psgtr.yaml
F: drivers/phy/xilinx/phy-zynqmp.c
XILLYBUS DRIVER
M: Eli Billauer <eli.billauer@gmail.com>
L: linux-kernel@vger.kernel.org

View File

@ -70,5 +70,6 @@ source "drivers/phy/st/Kconfig"
source "drivers/phy/tegra/Kconfig"
source "drivers/phy/ti/Kconfig"
source "drivers/phy/intel/Kconfig"
source "drivers/phy/xilinx/Kconfig"
endmenu

View File

@ -8,24 +8,25 @@ obj-$(CONFIG_GENERIC_PHY_MIPI_DPHY) += phy-core-mipi-dphy.o
obj-$(CONFIG_PHY_LPC18XX_USB_OTG) += phy-lpc18xx-usb-otg.o
obj-$(CONFIG_PHY_XGENE) += phy-xgene.o
obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o
obj-$(CONFIG_ARCH_SUNXI) += allwinner/
obj-$(CONFIG_ARCH_MESON) += amlogic/
obj-$(CONFIG_ARCH_MEDIATEK) += mediatek/
obj-$(CONFIG_ARCH_RENESAS) += renesas/
obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
obj-$(CONFIG_ARCH_TEGRA) += tegra/
obj-y += broadcom/ \
obj-y += allwinner/ \
amlogic/ \
broadcom/ \
cadence/ \
freescale/ \
hisilicon/ \
intel/ \
lantiq/ \
marvell/ \
mediatek/ \
motorola/ \
mscc/ \
qualcomm/ \
ralink/ \
renesas/ \
rockchip/ \
samsung/ \
socionext/ \
st/ \
ti/
tegra/ \
ti/ \
xilinx/

View File

@ -22,7 +22,7 @@ config PHY_SUN4I_USB
config PHY_SUN6I_MIPI_DPHY
tristate "Allwinner A31 MIPI D-PHY Support"
depends on ARCH_SUNXI || COMPILE_TEST
depends on HAS_IOMEM
depends on HAS_IOMEM && COMMON_CLK
depends on RESET_CONTROLLER
select GENERIC_PHY
select GENERIC_PHY_MIPI_DPHY

View File

@ -7,7 +7,7 @@
* Based on code from
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
*
* Modelled after: Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY driver
* Modelled after: Samsung S5P/Exynos SoC series MIPI CSIS/DSIM DPHY driver
* Copyright (C) 2013 Samsung Electronics Co., Ltd.
* Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
*/
@ -16,6 +16,7 @@
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/extcon-provider.h>
#include <linux/gpio/consumer.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>

View File

@ -233,7 +233,7 @@ static int sun6i_dphy_exit(struct phy *phy)
}
static struct phy_ops sun6i_dphy_ops = {
static const struct phy_ops sun6i_dphy_ops = {
.configure = sun6i_dphy_configure,
.power_on = sun6i_dphy_power_on,
.power_off = sun6i_dphy_power_off,
@ -241,7 +241,7 @@ static struct phy_ops sun6i_dphy_ops = {
.exit = sun6i_dphy_exit,
};
static struct regmap_config sun6i_dphy_regmap_config = {
static const struct regmap_config sun6i_dphy_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,

View File

@ -2,6 +2,14 @@
#
# Phy drivers for Broadcom platforms
#
config PHY_BCM63XX_USBH
tristate "BCM63xx USBH PHY driver"
depends on BMIPS_GENERIC || COMPILE_TEST
select GENERIC_PHY
help
Enable this to support the BCM63xx USBH PHY driver.
If unsure, say N.
config PHY_CYGNUS_PCIE
tristate "Broadcom Cygnus PCIe PHY driver"
depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST)

View File

@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_PHY_BCM63XX_USBH) += phy-bcm63xx-usbh.o
obj-$(CONFIG_PHY_CYGNUS_PCIE) += phy-bcm-cygnus-pcie.o
obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o
obj-$(CONFIG_PHY_BCM_NS_USB2) += phy-bcm-ns-usb2.o

View File

@ -0,0 +1,457 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* BCM6328 USBH PHY Controller Driver
*
* Copyright (C) 2020 Álvaro Fernández Rojas <noltari@gmail.com>
* Copyright (C) 2015 Simon Arlott
*
* Derived from bcm963xx_4.12L.06B_consumer/kernel/linux/arch/mips/bcm963xx/setup.c:
* Copyright (C) 2002 Broadcom Corporation
*
* Derived from OpenWrt patches:
* Copyright (C) 2013 Jonas Gorski <jonas.gorski@gmail.com>
* Copyright (C) 2013 Florian Fainelli <f.fainelli@gmail.com>
* Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr>
*/
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
/* USBH control register offsets */
enum usbh_regs {
USBH_BRT_CONTROL1 = 0,
USBH_BRT_CONTROL2,
USBH_BRT_STATUS1,
USBH_BRT_STATUS2,
USBH_UTMI_CONTROL1,
#define USBH_UC1_DEV_MODE_SEL BIT(0)
USBH_TEST_PORT_CONTROL,
USBH_PLL_CONTROL1,
#define USBH_PLLC_REFCLKSEL_SHIFT 0
#define USBH_PLLC_REFCLKSEL_MASK (0x3 << USBH_PLLC_REFCLKSEL_SHIFT)
#define USBH_PLLC_CLKSEL_SHIFT 2
#define USBH_PLLC_CLKSEL_MASK (0x3 << USBH_PLLC_CLKSEL_MASK)
#define USBH_PLLC_XTAL_PWRDWNB BIT(4)
#define USBH_PLLC_PLL_PWRDWNB BIT(5)
#define USBH_PLLC_PLL_CALEN BIT(6)
#define USBH_PLLC_PHYPLL_BYP BIT(7)
#define USBH_PLLC_PLL_RESET BIT(8)
#define USBH_PLLC_PLL_IDDQ_PWRDN BIT(9)
#define USBH_PLLC_PLL_PWRDN_DELAY BIT(10)
#define USBH_6318_PLLC_PLL_SUSPEND_EN BIT(27)
#define USBH_6318_PLLC_PHYPLL_BYP BIT(29)
#define USBH_6318_PLLC_PLL_RESET BIT(30)
#define USBH_6318_PLLC_PLL_IDDQ_PWRDN BIT(31)
USBH_SWAP_CONTROL,
#define USBH_SC_OHCI_DATA_SWAP BIT(0)
#define USBH_SC_OHCI_ENDIAN_SWAP BIT(1)
#define USBH_SC_OHCI_LOGICAL_ADDR_EN BIT(2)
#define USBH_SC_EHCI_DATA_SWAP BIT(3)
#define USBH_SC_EHCI_ENDIAN_SWAP BIT(4)
#define USBH_SC_EHCI_LOGICAL_ADDR_EN BIT(5)
#define USBH_SC_USB_DEVICE_SEL BIT(6)
USBH_GENERIC_CONTROL,
#define USBH_GC_PLL_SUSPEND_EN BIT(1)
USBH_FRAME_ADJUST_VALUE,
USBH_SETUP,
#define USBH_S_IOC BIT(4)
#define USBH_S_IPP BIT(5)
USBH_MDIO,
USBH_MDIO32,
USBH_USB_SIM_CONTROL,
#define USBH_USC_LADDR_SEL BIT(5)
__USBH_ENUM_SIZE
};
struct bcm63xx_usbh_phy_variant {
/* Registers */
long regs[__USBH_ENUM_SIZE];
/* PLLC bits to set/clear for power on */
u32 power_pllc_clr;
u32 power_pllc_set;
/* Setup bits to set/clear for power on */
u32 setup_clr;
u32 setup_set;
/* Swap Control bits to set */
u32 swapctl_dev_set;
/* Test Port Control value to set if non-zero */
u32 tpc_val;
/* USB Sim Control bits to set */
u32 usc_set;
/* UTMI Control 1 bits to set */
u32 utmictl1_dev_set;
};
struct bcm63xx_usbh_phy {
void __iomem *base;
struct clk *usbh_clk;
struct clk *usb_ref_clk;
struct reset_control *reset;
const struct bcm63xx_usbh_phy_variant *variant;
bool device_mode;
};
static const struct bcm63xx_usbh_phy_variant usbh_bcm6318 = {
.regs = {
[USBH_BRT_CONTROL1] = -1,
[USBH_BRT_CONTROL2] = -1,
[USBH_BRT_STATUS1] = -1,
[USBH_BRT_STATUS2] = -1,
[USBH_UTMI_CONTROL1] = 0x2c,
[USBH_TEST_PORT_CONTROL] = 0x1c,
[USBH_PLL_CONTROL1] = 0x04,
[USBH_SWAP_CONTROL] = 0x0c,
[USBH_GENERIC_CONTROL] = -1,
[USBH_FRAME_ADJUST_VALUE] = 0x08,
[USBH_SETUP] = 0x00,
[USBH_MDIO] = 0x14,
[USBH_MDIO32] = 0x18,
[USBH_USB_SIM_CONTROL] = 0x20,
},
.power_pllc_clr = USBH_6318_PLLC_PLL_IDDQ_PWRDN,
.power_pllc_set = USBH_6318_PLLC_PLL_SUSPEND_EN,
.setup_set = USBH_S_IOC,
.swapctl_dev_set = USBH_SC_USB_DEVICE_SEL,
.usc_set = USBH_USC_LADDR_SEL,
.utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL,
};
static const struct bcm63xx_usbh_phy_variant usbh_bcm6328 = {
.regs = {
[USBH_BRT_CONTROL1] = 0x00,
[USBH_BRT_CONTROL2] = 0x04,
[USBH_BRT_STATUS1] = 0x08,
[USBH_BRT_STATUS2] = 0x0c,
[USBH_UTMI_CONTROL1] = 0x10,
[USBH_TEST_PORT_CONTROL] = 0x14,
[USBH_PLL_CONTROL1] = 0x18,
[USBH_SWAP_CONTROL] = 0x1c,
[USBH_GENERIC_CONTROL] = 0x20,
[USBH_FRAME_ADJUST_VALUE] = 0x24,
[USBH_SETUP] = 0x28,
[USBH_MDIO] = 0x2c,
[USBH_MDIO32] = 0x30,
[USBH_USB_SIM_CONTROL] = 0x34,
},
.setup_set = USBH_S_IOC,
.swapctl_dev_set = USBH_SC_USB_DEVICE_SEL,
.utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL,
};
static const struct bcm63xx_usbh_phy_variant usbh_bcm6358 = {
.regs = {
[USBH_BRT_CONTROL1] = -1,
[USBH_BRT_CONTROL2] = -1,
[USBH_BRT_STATUS1] = -1,
[USBH_BRT_STATUS2] = -1,
[USBH_UTMI_CONTROL1] = -1,
[USBH_TEST_PORT_CONTROL] = 0x24,
[USBH_PLL_CONTROL1] = -1,
[USBH_SWAP_CONTROL] = 0x00,
[USBH_GENERIC_CONTROL] = -1,
[USBH_FRAME_ADJUST_VALUE] = -1,
[USBH_SETUP] = -1,
[USBH_MDIO] = -1,
[USBH_MDIO32] = -1,
[USBH_USB_SIM_CONTROL] = -1,
},
/*
* The magic value comes for the original vendor BSP
* and is needed for USB to work. Datasheet does not
* help, so the magic value is used as-is.
*/
.tpc_val = 0x1c0020,
};
static const struct bcm63xx_usbh_phy_variant usbh_bcm6368 = {
.regs = {
[USBH_BRT_CONTROL1] = 0x00,
[USBH_BRT_CONTROL2] = 0x04,
[USBH_BRT_STATUS1] = 0x08,
[USBH_BRT_STATUS2] = 0x0c,
[USBH_UTMI_CONTROL1] = 0x10,
[USBH_TEST_PORT_CONTROL] = 0x14,
[USBH_PLL_CONTROL1] = 0x18,
[USBH_SWAP_CONTROL] = 0x1c,
[USBH_GENERIC_CONTROL] = -1,
[USBH_FRAME_ADJUST_VALUE] = 0x24,
[USBH_SETUP] = 0x28,
[USBH_MDIO] = 0x2c,
[USBH_MDIO32] = 0x30,
[USBH_USB_SIM_CONTROL] = 0x34,
},
.power_pllc_clr = USBH_PLLC_PLL_IDDQ_PWRDN | USBH_PLLC_PLL_PWRDN_DELAY,
.setup_set = USBH_S_IOC,
.swapctl_dev_set = USBH_SC_USB_DEVICE_SEL,
.utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL,
};
static const struct bcm63xx_usbh_phy_variant usbh_bcm63268 = {
.regs = {
[USBH_BRT_CONTROL1] = 0x00,
[USBH_BRT_CONTROL2] = 0x04,
[USBH_BRT_STATUS1] = 0x08,
[USBH_BRT_STATUS2] = 0x0c,
[USBH_UTMI_CONTROL1] = 0x10,
[USBH_TEST_PORT_CONTROL] = 0x14,
[USBH_PLL_CONTROL1] = 0x18,
[USBH_SWAP_CONTROL] = 0x1c,
[USBH_GENERIC_CONTROL] = 0x20,
[USBH_FRAME_ADJUST_VALUE] = 0x24,
[USBH_SETUP] = 0x28,
[USBH_MDIO] = 0x2c,
[USBH_MDIO32] = 0x30,
[USBH_USB_SIM_CONTROL] = 0x34,
},
.power_pllc_clr = USBH_PLLC_PLL_IDDQ_PWRDN | USBH_PLLC_PLL_PWRDN_DELAY,
.setup_clr = USBH_S_IPP,
.setup_set = USBH_S_IOC,
.swapctl_dev_set = USBH_SC_USB_DEVICE_SEL,
.utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL,
};
static inline bool usbh_has_reg(struct bcm63xx_usbh_phy *usbh, int reg)
{
return (usbh->variant->regs[reg] >= 0);
}
static inline u32 usbh_readl(struct bcm63xx_usbh_phy *usbh, int reg)
{
return __raw_readl(usbh->base + usbh->variant->regs[reg]);
}
static inline void usbh_writel(struct bcm63xx_usbh_phy *usbh, int reg,
u32 value)
{
__raw_writel(value, usbh->base + usbh->variant->regs[reg]);
}
static int bcm63xx_usbh_phy_init(struct phy *phy)
{
struct bcm63xx_usbh_phy *usbh = phy_get_drvdata(phy);
int ret;
ret = clk_prepare_enable(usbh->usbh_clk);
if (ret) {
dev_err(&phy->dev, "unable to enable usbh clock: %d\n", ret);
return ret;
}
ret = clk_prepare_enable(usbh->usb_ref_clk);
if (ret) {
dev_err(&phy->dev, "unable to enable usb_ref clock: %d\n", ret);
clk_disable_unprepare(usbh->usbh_clk);
return ret;
}
ret = reset_control_reset(usbh->reset);
if (ret) {
dev_err(&phy->dev, "unable to reset device: %d\n", ret);
clk_disable_unprepare(usbh->usb_ref_clk);
clk_disable_unprepare(usbh->usbh_clk);
return ret;
}
/* Configure to work in native CPU endian */
if (usbh_has_reg(usbh, USBH_SWAP_CONTROL)) {
u32 val = usbh_readl(usbh, USBH_SWAP_CONTROL);
val |= USBH_SC_EHCI_DATA_SWAP;
val &= ~USBH_SC_EHCI_ENDIAN_SWAP;
val |= USBH_SC_OHCI_DATA_SWAP;
val &= ~USBH_SC_OHCI_ENDIAN_SWAP;
if (usbh->device_mode && usbh->variant->swapctl_dev_set)
val |= usbh->variant->swapctl_dev_set;
usbh_writel(usbh, USBH_SWAP_CONTROL, val);
}
if (usbh_has_reg(usbh, USBH_SETUP)) {
u32 val = usbh_readl(usbh, USBH_SETUP);
val |= usbh->variant->setup_set;
val &= ~usbh->variant->setup_clr;
usbh_writel(usbh, USBH_SETUP, val);
}
if (usbh_has_reg(usbh, USBH_USB_SIM_CONTROL)) {
u32 val = usbh_readl(usbh, USBH_USB_SIM_CONTROL);
val |= usbh->variant->usc_set;
usbh_writel(usbh, USBH_USB_SIM_CONTROL, val);
}
if (usbh->variant->tpc_val &&
usbh_has_reg(usbh, USBH_TEST_PORT_CONTROL))
usbh_writel(usbh, USBH_TEST_PORT_CONTROL,
usbh->variant->tpc_val);
if (usbh->device_mode &&
usbh_has_reg(usbh, USBH_UTMI_CONTROL1) &&
usbh->variant->utmictl1_dev_set) {
u32 val = usbh_readl(usbh, USBH_UTMI_CONTROL1);
val |= usbh->variant->utmictl1_dev_set;
usbh_writel(usbh, USBH_UTMI_CONTROL1, val);
}
return 0;
}
static int bcm63xx_usbh_phy_power_on(struct phy *phy)
{
struct bcm63xx_usbh_phy *usbh = phy_get_drvdata(phy);
if (usbh_has_reg(usbh, USBH_PLL_CONTROL1)) {
u32 val = usbh_readl(usbh, USBH_PLL_CONTROL1);
val |= usbh->variant->power_pllc_set;
val &= ~usbh->variant->power_pllc_clr;
usbh_writel(usbh, USBH_PLL_CONTROL1, val);
}
return 0;
}
static int bcm63xx_usbh_phy_power_off(struct phy *phy)
{
struct bcm63xx_usbh_phy *usbh = phy_get_drvdata(phy);
if (usbh_has_reg(usbh, USBH_PLL_CONTROL1)) {
u32 val = usbh_readl(usbh, USBH_PLL_CONTROL1);
val &= ~usbh->variant->power_pllc_set;
val |= usbh->variant->power_pllc_clr;
usbh_writel(usbh, USBH_PLL_CONTROL1, val);
}
return 0;
}
static int bcm63xx_usbh_phy_exit(struct phy *phy)
{
struct bcm63xx_usbh_phy *usbh = phy_get_drvdata(phy);
clk_disable_unprepare(usbh->usbh_clk);
clk_disable_unprepare(usbh->usb_ref_clk);
return 0;
}
static const struct phy_ops bcm63xx_usbh_phy_ops = {
.exit = bcm63xx_usbh_phy_exit,
.init = bcm63xx_usbh_phy_init,
.power_off = bcm63xx_usbh_phy_power_off,
.power_on = bcm63xx_usbh_phy_power_on,
.owner = THIS_MODULE,
};
static struct phy *bcm63xx_usbh_phy_xlate(struct device *dev,
struct of_phandle_args *args)
{
struct bcm63xx_usbh_phy *usbh = dev_get_drvdata(dev);
usbh->device_mode = !!args->args[0];
return of_phy_simple_xlate(dev, args);
}
static int __init bcm63xx_usbh_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct bcm63xx_usbh_phy *usbh;
const struct bcm63xx_usbh_phy_variant *variant;
struct phy *phy;
struct phy_provider *phy_provider;
usbh = devm_kzalloc(dev, sizeof(*usbh), GFP_KERNEL);
if (!usbh)
return -ENOMEM;
variant = device_get_match_data(dev);
if (!variant)
return -EINVAL;
usbh->variant = variant;
usbh->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(usbh->base))
return PTR_ERR(usbh->base);
usbh->reset = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(usbh->reset)) {
if (PTR_ERR(usbh->reset) != -EPROBE_DEFER)
dev_err(dev, "failed to get reset\n");
return PTR_ERR(usbh->reset);
}
usbh->usbh_clk = devm_clk_get_optional(dev, "usbh");
if (IS_ERR(usbh->usbh_clk))
return PTR_ERR(usbh->usbh_clk);
usbh->usb_ref_clk = devm_clk_get_optional(dev, "usb_ref");
if (IS_ERR(usbh->usb_ref_clk))
return PTR_ERR(usbh->usb_ref_clk);
phy = devm_phy_create(dev, NULL, &bcm63xx_usbh_phy_ops);
if (IS_ERR(phy)) {
dev_err(dev, "failed to create PHY\n");
return PTR_ERR(phy);
}
platform_set_drvdata(pdev, usbh);
phy_set_drvdata(phy, usbh);
phy_provider = devm_of_phy_provider_register(dev,
bcm63xx_usbh_phy_xlate);
if (IS_ERR(phy_provider)) {
dev_err(dev, "failed to register PHY provider\n");
return PTR_ERR(phy_provider);
}
dev_dbg(dev, "Registered BCM63xx USB PHY driver\n");
return 0;
}
static const struct of_device_id bcm63xx_usbh_phy_ids[] __initconst = {
{ .compatible = "brcm,bcm6318-usbh-phy", .data = &usbh_bcm6318 },
{ .compatible = "brcm,bcm6328-usbh-phy", .data = &usbh_bcm6328 },
{ .compatible = "brcm,bcm6358-usbh-phy", .data = &usbh_bcm6358 },
{ .compatible = "brcm,bcm6362-usbh-phy", .data = &usbh_bcm6368 },
{ .compatible = "brcm,bcm6368-usbh-phy", .data = &usbh_bcm6368 },
{ .compatible = "brcm,bcm63268-usbh-phy", .data = &usbh_bcm63268 },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, bcm63xx_usbh_phy_ids);
static struct platform_driver bcm63xx_usbh_phy_driver __refdata = {
.driver = {
.name = "bcm63xx-usbh-phy",
.of_match_table = bcm63xx_usbh_phy_ids,
},
.probe = bcm63xx_usbh_phy_probe,
};
module_platform_driver(bcm63xx_usbh_phy_driver);
MODULE_DESCRIPTION("BCM63xx USBH PHY driver");
MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
MODULE_AUTHOR("Simon Arlott");
MODULE_LICENSE("GPL");

View File

@ -88,7 +88,7 @@
#define TB_ADDR_TX_RCVDETSC_CTRL 0x4124
/* TB_ADDR_TX_RCVDETSC_CTRL */
#define RXDET_IN_P3_32KHZ BIT(1)
#define RXDET_IN_P3_32KHZ BIT(0)
struct cdns_reg_pairs {
u16 val;

View File

@ -41,6 +41,7 @@ struct a38x_comphy_lane {
struct a38x_comphy {
void __iomem *base;
void __iomem *conf;
struct device *dev;
struct a38x_comphy_lane lane[MAX_A38X_COMPHY];
};
@ -54,6 +55,21 @@ static const u8 gbe_mux[MAX_A38X_COMPHY][MAX_A38X_PORTS] = {
{ 0, 0, 3 },
};
static void a38x_set_conf(struct a38x_comphy_lane *lane, bool enable)
{
struct a38x_comphy *priv = lane->priv;
u32 conf;
if (priv->conf) {
conf = readl_relaxed(priv->conf);
if (enable)
conf |= BIT(lane->port);
else
conf &= ~BIT(lane->port);
writel(conf, priv->conf);
}
}
static void a38x_comphy_set_reg(struct a38x_comphy_lane *lane,
unsigned int offset, u32 mask, u32 value)
{
@ -97,6 +113,7 @@ static int a38x_comphy_set_mode(struct phy *phy, enum phy_mode mode, int sub)
{
struct a38x_comphy_lane *lane = phy_get_drvdata(phy);
unsigned int gen;
int ret;
if (mode != PHY_MODE_ETHERNET)
return -EINVAL;
@ -115,13 +132,20 @@ static int a38x_comphy_set_mode(struct phy *phy, enum phy_mode mode, int sub)
return -EINVAL;
}
a38x_set_conf(lane, false);
a38x_comphy_set_speed(lane, gen, gen);
return a38x_comphy_poll(lane, COMPHY_STAT1,
COMPHY_STAT1_PLL_RDY_TX |
COMPHY_STAT1_PLL_RDY_RX,
COMPHY_STAT1_PLL_RDY_TX |
COMPHY_STAT1_PLL_RDY_RX);
ret = a38x_comphy_poll(lane, COMPHY_STAT1,
COMPHY_STAT1_PLL_RDY_TX |
COMPHY_STAT1_PLL_RDY_RX,
COMPHY_STAT1_PLL_RDY_TX |
COMPHY_STAT1_PLL_RDY_RX);
if (ret == 0)
a38x_set_conf(lane, true);
return ret;
}
static const struct phy_ops a38x_comphy_ops = {
@ -174,14 +198,21 @@ static int a38x_comphy_probe(struct platform_device *pdev)
if (!priv)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return PTR_ERR(base);
priv->dev = &pdev->dev;
priv->base = base;
/* Optional */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "conf");
if (res) {
priv->conf = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(priv->conf))
return PTR_ERR(priv->conf);
}
for_each_available_child_of_node(pdev->dev.of_node, child) {
struct phy *phy;
int ret;

View File

@ -72,7 +72,7 @@ struct mvebu_a3700_utmi_caps {
* struct mvebu_a3700_utmi - PHY driver data
*
* @regs: PHY registers
* @usb_mis: Regmap with USB miscellaneous registers including PHY ones
* @usb_misc: Regmap with USB miscellaneous registers including PHY ones
* @caps: PHY capabilities
* @phy: PHY handle
*/

View File

@ -178,6 +178,7 @@ static const struct phy_ops gpio_usb_ops = {
/**
* phy_mdm6600_cmd() - send a command request to mdm6600
* @ddata: device driver data
* @val: value of cmd to be set
*
* Configures the three command request GPIOs to the specified value.
*/
@ -194,7 +195,7 @@ static void phy_mdm6600_cmd(struct phy_mdm6600 *ddata, int val)
/**
* phy_mdm6600_status() - read mdm6600 status lines
* @ddata: device driver data
* @work: work structure
*/
static void phy_mdm6600_status(struct work_struct *work)
{

View File

@ -1062,6 +1062,7 @@ EXPORT_SYMBOL_GPL(__of_phy_provider_register);
* __devm_of_phy_provider_register() - create/register phy provider with the
* framework
* @dev: struct device of the phy provider
* @children: device node containing children (if different from dev->of_node)
* @owner: the module owner containing of_xlate
* @of_xlate: function pointer to obtain phy instance from phy provider
*
@ -1117,12 +1118,14 @@ EXPORT_SYMBOL_GPL(of_phy_provider_unregister);
/**
* devm_of_phy_provider_unregister() - remove phy provider from the framework
* @dev: struct device of the phy provider
* @phy_provider: phy provider returned by of_phy_provider_register()
*
* destroys the devres associated with this phy provider and invokes
* of_phy_provider_unregister to unregister the phy provider.
*/
void devm_of_phy_provider_unregister(struct device *dev,
struct phy_provider *phy_provider) {
struct phy_provider *phy_provider)
{
int r;
r = devres_destroy(dev, devm_phy_provider_release, devm_phy_match,

View File

@ -1615,7 +1615,7 @@ static struct phy *xgene_phy_xlate(struct device *dev,
if (args->args_count <= 0)
return ERR_PTR(-EINVAL);
if (args->args[0] < MODE_SATA || args->args[0] >= MODE_MAX)
if (args->args[0] >= MODE_MAX)
return ERR_PTR(-EINVAL);
ctx->mode = args->args[0];

View File

@ -59,30 +59,6 @@ config PHY_QCOM_QUSB2
PHY which is usually paired with either the ChipIdea or Synopsys DWC3
USB IPs on MSM SOCs.
config PHY_QCOM_UFS
tristate "Qualcomm UFS PHY driver"
depends on OF && ARCH_QCOM
select GENERIC_PHY
help
Support for UFS PHY on QCOM chipsets.
if PHY_QCOM_UFS
config PHY_QCOM_UFS_14NM
tristate
default PHY_QCOM_UFS
help
Support for 14nm UFS QMP phy present on QCOM chipsets.
config PHY_QCOM_UFS_20NM
tristate
default PHY_QCOM_UFS
depends on BROKEN
help
Support for 20nm UFS QMP phy present on QCOM chipsets.
endif
config PHY_QCOM_USB_HS
tristate "Qualcomm USB HS PHY module"
depends on USB_ULPI_BUS
@ -128,3 +104,13 @@ config PHY_QCOM_USB_SS
help
Enable this to support the Super-Speed USB transceiver on various
Qualcomm chipsets.
config PHY_QCOM_IPQ806X_USB
tristate "Qualcomm IPQ806x DWC3 USB PHY driver"
depends on HAS_IOMEM
depends on OF && (ARCH_QCOM || COMPILE_TEST)
select GENERIC_PHY
help
This option enables support for the Synopsis PHYs present inside the
Qualcomm USB3.0 DWC3 controller on ipq806x SoC. This driver supports
both HS and SS PHY controllers.

View File

@ -6,11 +6,9 @@ obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o
obj-$(CONFIG_PHY_QCOM_PCIE2) += phy-qcom-pcie2.o
obj-$(CONFIG_PHY_QCOM_QMP) += phy-qcom-qmp.o
obj-$(CONFIG_PHY_QCOM_QUSB2) += phy-qcom-qusb2.o
obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs.o
obj-$(CONFIG_PHY_QCOM_UFS_14NM) += phy-qcom-ufs-qmp-14nm.o
obj-$(CONFIG_PHY_QCOM_UFS_20NM) += phy-qcom-ufs-qmp-20nm.o
obj-$(CONFIG_PHY_QCOM_USB_HS) += phy-qcom-usb-hs.o
obj-$(CONFIG_PHY_QCOM_USB_HSIC) += phy-qcom-usb-hsic.o
obj-$(CONFIG_PHY_QCOM_USB_HS_28NM) += phy-qcom-usb-hs-28nm.o
obj-$(CONFIG_PHY_QCOM_USB_SS) += phy-qcom-usb-ss.o
obj-$(CONFIG_PHY_QCOM_USB_SNPS_FEMTO_V2)+= phy-qcom-snps-femto-v2.o
obj-$(CONFIG_PHY_QCOM_IPQ806X_USB) += phy-qcom-ipq806x-usb.o

View File

@ -0,0 +1,571 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>
/* USB QSCRATCH Hardware registers */
#define QSCRATCH_GENERAL_CFG (0x08)
#define HSUSB_PHY_CTRL_REG (0x10)
/* PHY_CTRL_REG */
#define HSUSB_CTRL_DMSEHV_CLAMP BIT(24)
#define HSUSB_CTRL_USB2_SUSPEND BIT(23)
#define HSUSB_CTRL_UTMI_CLK_EN BIT(21)
#define HSUSB_CTRL_UTMI_OTG_VBUS_VALID BIT(20)
#define HSUSB_CTRL_USE_CLKCORE BIT(18)
#define HSUSB_CTRL_DPSEHV_CLAMP BIT(17)
#define HSUSB_CTRL_COMMONONN BIT(11)
#define HSUSB_CTRL_ID_HV_CLAMP BIT(9)
#define HSUSB_CTRL_OTGSESSVLD_CLAMP BIT(8)
#define HSUSB_CTRL_CLAMP_EN BIT(7)
#define HSUSB_CTRL_RETENABLEN BIT(1)
#define HSUSB_CTRL_POR BIT(0)
/* QSCRATCH_GENERAL_CFG */
#define HSUSB_GCFG_XHCI_REV BIT(2)
/* USB QSCRATCH Hardware registers */
#define SSUSB_PHY_CTRL_REG (0x00)
#define SSUSB_PHY_PARAM_CTRL_1 (0x04)
#define SSUSB_PHY_PARAM_CTRL_2 (0x08)
#define CR_PROTOCOL_DATA_IN_REG (0x0c)
#define CR_PROTOCOL_DATA_OUT_REG (0x10)
#define CR_PROTOCOL_CAP_ADDR_REG (0x14)
#define CR_PROTOCOL_CAP_DATA_REG (0x18)
#define CR_PROTOCOL_READ_REG (0x1c)
#define CR_PROTOCOL_WRITE_REG (0x20)
/* PHY_CTRL_REG */
#define SSUSB_CTRL_REF_USE_PAD BIT(28)
#define SSUSB_CTRL_TEST_POWERDOWN BIT(27)
#define SSUSB_CTRL_LANE0_PWR_PRESENT BIT(24)
#define SSUSB_CTRL_SS_PHY_EN BIT(8)
#define SSUSB_CTRL_SS_PHY_RESET BIT(7)
/* SSPHY control registers - Does this need 0x30? */
#define SSPHY_CTRL_RX_OVRD_IN_HI(lane) (0x1006 + 0x100 * (lane))
#define SSPHY_CTRL_TX_OVRD_DRV_LO(lane) (0x1002 + 0x100 * (lane))
/* SSPHY SoC version specific values */
#define SSPHY_RX_EQ_VALUE 4 /* Override value for rx_eq */
/* Override value for transmit preemphasis */
#define SSPHY_TX_DEEMPH_3_5DB 23
/* Override value for mpll */
#define SSPHY_MPLL_VALUE 0
/* QSCRATCH PHY_PARAM_CTRL1 fields */
#define PHY_PARAM_CTRL1_TX_FULL_SWING_MASK GENMASK(26, 19)
#define PHY_PARAM_CTRL1_TX_DEEMPH_6DB_MASK GENMASK(19, 13)
#define PHY_PARAM_CTRL1_TX_DEEMPH_3_5DB_MASK GENMASK(13, 7)
#define PHY_PARAM_CTRL1_LOS_BIAS_MASK GENMASK(7, 2)
#define PHY_PARAM_CTRL1_MASK \
(PHY_PARAM_CTRL1_TX_FULL_SWING_MASK | \
PHY_PARAM_CTRL1_TX_DEEMPH_6DB_MASK | \
PHY_PARAM_CTRL1_TX_DEEMPH_3_5DB_MASK | \
PHY_PARAM_CTRL1_LOS_BIAS_MASK)
#define PHY_PARAM_CTRL1_TX_FULL_SWING(x) \
(((x) << 20) & PHY_PARAM_CTRL1_TX_FULL_SWING_MASK)
#define PHY_PARAM_CTRL1_TX_DEEMPH_6DB(x) \
(((x) << 14) & PHY_PARAM_CTRL1_TX_DEEMPH_6DB_MASK)
#define PHY_PARAM_CTRL1_TX_DEEMPH_3_5DB(x) \
(((x) << 8) & PHY_PARAM_CTRL1_TX_DEEMPH_3_5DB_MASK)
#define PHY_PARAM_CTRL1_LOS_BIAS(x) \
(((x) << 3) & PHY_PARAM_CTRL1_LOS_BIAS_MASK)
/* RX OVRD IN HI bits */
#define RX_OVRD_IN_HI_RX_RESET_OVRD BIT(13)
#define RX_OVRD_IN_HI_RX_RX_RESET BIT(12)
#define RX_OVRD_IN_HI_RX_EQ_OVRD BIT(11)
#define RX_OVRD_IN_HI_RX_EQ_MASK GENMASK(10, 7)
#define RX_OVRD_IN_HI_RX_EQ(x) ((x) << 8)
#define RX_OVRD_IN_HI_RX_EQ_EN_OVRD BIT(7)
#define RX_OVRD_IN_HI_RX_EQ_EN BIT(6)
#define RX_OVRD_IN_HI_RX_LOS_FILTER_OVRD BIT(5)
#define RX_OVRD_IN_HI_RX_LOS_FILTER_MASK GENMASK(4, 2)
#define RX_OVRD_IN_HI_RX_RATE_OVRD BIT(2)
#define RX_OVRD_IN_HI_RX_RATE_MASK GENMASK(2, 0)
/* TX OVRD DRV LO register bits */
#define TX_OVRD_DRV_LO_AMPLITUDE_MASK GENMASK(6, 0)
#define TX_OVRD_DRV_LO_PREEMPH_MASK GENMASK(13, 6)
#define TX_OVRD_DRV_LO_PREEMPH(x) ((x) << 7)
#define TX_OVRD_DRV_LO_EN BIT(14)
/* MPLL bits */
#define SSPHY_MPLL_MASK GENMASK(8, 5)
#define SSPHY_MPLL(x) ((x) << 5)
/* SS CAP register bits */
#define SS_CR_CAP_ADDR_REG BIT(0)
#define SS_CR_CAP_DATA_REG BIT(0)
#define SS_CR_READ_REG BIT(0)
#define SS_CR_WRITE_REG BIT(0)
struct usb_phy {
void __iomem *base;
struct device *dev;
struct clk *xo_clk;
struct clk *ref_clk;
u32 rx_eq;
u32 tx_deamp_3_5db;
u32 mpll;
};
struct phy_drvdata {
struct phy_ops ops;
u32 clk_rate;
};
/**
* Write register and read back masked value to confirm it is written
*
* @base - QCOM DWC3 PHY base virtual address.
* @offset - register offset.
* @mask - register bitmask specifying what should be updated
* @val - value to write.
*/
static inline void usb_phy_write_readback(struct usb_phy *phy_dwc3,
u32 offset,
const u32 mask, u32 val)
{
u32 write_val, tmp = readl(phy_dwc3->base + offset);
tmp &= ~mask; /* retain other bits */
write_val = tmp | val;
writel(write_val, phy_dwc3->base + offset);
/* Read back to see if val was written */
tmp = readl(phy_dwc3->base + offset);
tmp &= mask; /* clear other bits */
if (tmp != val)
dev_err(phy_dwc3->dev, "write: %x to QSCRATCH: %x FAILED\n", val, offset);
}
static int wait_for_latch(void __iomem *addr)
{
u32 retry = 10;
while (true) {
if (!readl(addr))
break;
if (--retry == 0)
return -ETIMEDOUT;
usleep_range(10, 20);
}
return 0;
}
/**
* Write SSPHY register
*
* @base - QCOM DWC3 PHY base virtual address.
* @addr - SSPHY address to write.
* @val - value to write.
*/
static int usb_ss_write_phycreg(struct usb_phy *phy_dwc3,
u32 addr, u32 val)
{
int ret;
writel(addr, phy_dwc3->base + CR_PROTOCOL_DATA_IN_REG);
writel(SS_CR_CAP_ADDR_REG,
phy_dwc3->base + CR_PROTOCOL_CAP_ADDR_REG);
ret = wait_for_latch(phy_dwc3->base + CR_PROTOCOL_CAP_ADDR_REG);
if (ret)
goto err_wait;
writel(val, phy_dwc3->base + CR_PROTOCOL_DATA_IN_REG);
writel(SS_CR_CAP_DATA_REG,
phy_dwc3->base + CR_PROTOCOL_CAP_DATA_REG);
ret = wait_for_latch(phy_dwc3->base + CR_PROTOCOL_CAP_DATA_REG);
if (ret)
goto err_wait;
writel(SS_CR_WRITE_REG, phy_dwc3->base + CR_PROTOCOL_WRITE_REG);
ret = wait_for_latch(phy_dwc3->base + CR_PROTOCOL_WRITE_REG);
err_wait:
if (ret)
dev_err(phy_dwc3->dev, "timeout waiting for latch\n");
return ret;
}
/**
* Read SSPHY register.
*
* @base - QCOM DWC3 PHY base virtual address.
* @addr - SSPHY address to read.
*/
static int usb_ss_read_phycreg(struct usb_phy *phy_dwc3,
u32 addr, u32 *val)
{
int ret;
writel(addr, phy_dwc3->base + CR_PROTOCOL_DATA_IN_REG);
writel(SS_CR_CAP_ADDR_REG,
phy_dwc3->base + CR_PROTOCOL_CAP_ADDR_REG);
ret = wait_for_latch(phy_dwc3->base + CR_PROTOCOL_CAP_ADDR_REG);
if (ret)
goto err_wait;
/*
* Due to hardware bug, first read of SSPHY register might be
* incorrect. Hence as workaround, SW should perform SSPHY register
* read twice, but use only second read and ignore first read.
*/
writel(SS_CR_READ_REG, phy_dwc3->base + CR_PROTOCOL_READ_REG);
ret = wait_for_latch(phy_dwc3->base + CR_PROTOCOL_READ_REG);
if (ret)
goto err_wait;
/* throwaway read */
readl(phy_dwc3->base + CR_PROTOCOL_DATA_OUT_REG);
writel(SS_CR_READ_REG, phy_dwc3->base + CR_PROTOCOL_READ_REG);
ret = wait_for_latch(phy_dwc3->base + CR_PROTOCOL_READ_REG);
if (ret)
goto err_wait;
*val = readl(phy_dwc3->base + CR_PROTOCOL_DATA_OUT_REG);
err_wait:
return ret;
}
static int qcom_ipq806x_usb_hs_phy_init(struct phy *phy)
{
struct usb_phy *phy_dwc3 = phy_get_drvdata(phy);
int ret;
u32 val;
ret = clk_prepare_enable(phy_dwc3->xo_clk);
if (ret)
return ret;
ret = clk_prepare_enable(phy_dwc3->ref_clk);
if (ret) {
clk_disable_unprepare(phy_dwc3->xo_clk);
return ret;
}
/*
* HSPHY Initialization: Enable UTMI clock, select 19.2MHz fsel
* enable clamping, and disable RETENTION (power-on default is ENABLED)
*/
val = HSUSB_CTRL_DPSEHV_CLAMP | HSUSB_CTRL_DMSEHV_CLAMP |
HSUSB_CTRL_RETENABLEN | HSUSB_CTRL_COMMONONN |
HSUSB_CTRL_OTGSESSVLD_CLAMP | HSUSB_CTRL_ID_HV_CLAMP |
HSUSB_CTRL_DPSEHV_CLAMP | HSUSB_CTRL_UTMI_OTG_VBUS_VALID |
HSUSB_CTRL_UTMI_CLK_EN | HSUSB_CTRL_CLAMP_EN | 0x70;
/* use core clock if external reference is not present */
if (!phy_dwc3->xo_clk)
val |= HSUSB_CTRL_USE_CLKCORE;
writel(val, phy_dwc3->base + HSUSB_PHY_CTRL_REG);
usleep_range(2000, 2200);
/* Disable (bypass) VBUS and ID filters */
writel(HSUSB_GCFG_XHCI_REV, phy_dwc3->base + QSCRATCH_GENERAL_CFG);
return 0;
}
static int qcom_ipq806x_usb_hs_phy_exit(struct phy *phy)
{
struct usb_phy *phy_dwc3 = phy_get_drvdata(phy);
clk_disable_unprepare(phy_dwc3->ref_clk);
clk_disable_unprepare(phy_dwc3->xo_clk);
return 0;
}
static int qcom_ipq806x_usb_ss_phy_init(struct phy *phy)
{
struct usb_phy *phy_dwc3 = phy_get_drvdata(phy);
int ret;
u32 data;
ret = clk_prepare_enable(phy_dwc3->xo_clk);
if (ret)
return ret;
ret = clk_prepare_enable(phy_dwc3->ref_clk);
if (ret) {
clk_disable_unprepare(phy_dwc3->xo_clk);
return ret;
}
/* reset phy */
data = readl(phy_dwc3->base + SSUSB_PHY_CTRL_REG);
writel(data | SSUSB_CTRL_SS_PHY_RESET,
phy_dwc3->base + SSUSB_PHY_CTRL_REG);
usleep_range(2000, 2200);
writel(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG);
/* clear REF_PAD if we don't have XO clk */
if (!phy_dwc3->xo_clk)
data &= ~SSUSB_CTRL_REF_USE_PAD;
else
data |= SSUSB_CTRL_REF_USE_PAD;
writel(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG);
/* wait for ref clk to become stable, this can take up to 30ms */
msleep(30);
data |= SSUSB_CTRL_SS_PHY_EN | SSUSB_CTRL_LANE0_PWR_PRESENT;
writel(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG);
/*
* 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 = usb_ss_read_phycreg(phy_dwc3, 0x102D, &data);
if (ret)
goto err_phy_trans;
data |= (1 << 7);
ret = usb_ss_write_phycreg(phy_dwc3, 0x102D, data);
if (ret)
goto err_phy_trans;
ret = usb_ss_read_phycreg(phy_dwc3, 0x1010, &data);
if (ret)
goto err_phy_trans;
data &= ~0xff0;
data |= 0x20;
ret = usb_ss_write_phycreg(phy_dwc3, 0x1010, data);
if (ret)
goto err_phy_trans;
/*
* 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 based on SoC version
* LANE0.RX_OVRD_IN_HI.RX_EQ_OVRD set to 1
*/
ret = usb_ss_read_phycreg(phy_dwc3, SSPHY_CTRL_RX_OVRD_IN_HI(0), &data);
if (ret)
goto err_phy_trans;
data &= ~RX_OVRD_IN_HI_RX_EQ_EN;
data |= RX_OVRD_IN_HI_RX_EQ_EN_OVRD;
data &= ~RX_OVRD_IN_HI_RX_EQ_MASK;
data |= RX_OVRD_IN_HI_RX_EQ(phy_dwc3->rx_eq);
data |= RX_OVRD_IN_HI_RX_EQ_OVRD;
ret = usb_ss_write_phycreg(phy_dwc3,
SSPHY_CTRL_RX_OVRD_IN_HI(0), data);
if (ret)
goto err_phy_trans;
/*
* Set EQ and TX launch amplitudes as follows
* LANE0.TX_OVRD_DRV_LO.PREEMPH set based on SoC version
* LANE0.TX_OVRD_DRV_LO.AMPLITUDE set to 110
* LANE0.TX_OVRD_DRV_LO.EN set to 1.
*/
ret = usb_ss_read_phycreg(phy_dwc3,
SSPHY_CTRL_TX_OVRD_DRV_LO(0), &data);
if (ret)
goto err_phy_trans;
data &= ~TX_OVRD_DRV_LO_PREEMPH_MASK;
data |= TX_OVRD_DRV_LO_PREEMPH(phy_dwc3->tx_deamp_3_5db);
data &= ~TX_OVRD_DRV_LO_AMPLITUDE_MASK;
data |= 0x6E;
data |= TX_OVRD_DRV_LO_EN;
ret = usb_ss_write_phycreg(phy_dwc3,
SSPHY_CTRL_TX_OVRD_DRV_LO(0), data);
if (ret)
goto err_phy_trans;
data = 0;
data &= ~SSPHY_MPLL_MASK;
data |= SSPHY_MPLL(phy_dwc3->mpll);
usb_ss_write_phycreg(phy_dwc3, 0x30, data);
/*
* Set the QSCRATCH PHY_PARAM_CTRL1 parameters as follows
* TX_FULL_SWING [26:20] amplitude to 110
* TX_DEEMPH_6DB [19:14] to 32
* TX_DEEMPH_3_5DB [13:8] set based on SoC version
* LOS_BIAS [7:3] to 9
*/
data = readl(phy_dwc3->base + SSUSB_PHY_PARAM_CTRL_1);
data &= ~PHY_PARAM_CTRL1_MASK;
data |= PHY_PARAM_CTRL1_TX_FULL_SWING(0x6e) |
PHY_PARAM_CTRL1_TX_DEEMPH_6DB(0x20) |
PHY_PARAM_CTRL1_TX_DEEMPH_3_5DB(phy_dwc3->tx_deamp_3_5db) |
PHY_PARAM_CTRL1_LOS_BIAS(0x9);
usb_phy_write_readback(phy_dwc3, SSUSB_PHY_PARAM_CTRL_1,
PHY_PARAM_CTRL1_MASK, data);
err_phy_trans:
return ret;
}
static int qcom_ipq806x_usb_ss_phy_exit(struct phy *phy)
{
struct usb_phy *phy_dwc3 = phy_get_drvdata(phy);
/* Sequence to put SSPHY in low power state:
* 1. Clear REF_PHY_EN in PHY_CTRL_REG
* 2. Clear REF_USE_PAD in PHY_CTRL_REG
* 3. Set TEST_POWERED_DOWN in PHY_CTRL_REG to enable PHY retention
*/
usb_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG,
SSUSB_CTRL_SS_PHY_EN, 0x0);
usb_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG,
SSUSB_CTRL_REF_USE_PAD, 0x0);
usb_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG,
SSUSB_CTRL_TEST_POWERDOWN, 0x0);
clk_disable_unprepare(phy_dwc3->ref_clk);
clk_disable_unprepare(phy_dwc3->xo_clk);
return 0;
}
static const struct phy_drvdata qcom_ipq806x_usb_hs_drvdata = {
.ops = {
.init = qcom_ipq806x_usb_hs_phy_init,
.exit = qcom_ipq806x_usb_hs_phy_exit,
.owner = THIS_MODULE,
},
.clk_rate = 60000000,
};
static const struct phy_drvdata qcom_ipq806x_usb_ss_drvdata = {
.ops = {
.init = qcom_ipq806x_usb_ss_phy_init,
.exit = qcom_ipq806x_usb_ss_phy_exit,
.owner = THIS_MODULE,
},
.clk_rate = 125000000,
};
static const struct of_device_id qcom_ipq806x_usb_phy_table[] = {
{ .compatible = "qcom,ipq806x-usb-phy-hs",
.data = &qcom_ipq806x_usb_hs_drvdata },
{ .compatible = "qcom,ipq806x-usb-phy-ss",
.data = &qcom_ipq806x_usb_ss_drvdata },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, qcom_ipq806x_usb_phy_table);
static int qcom_ipq806x_usb_phy_probe(struct platform_device *pdev)
{
struct resource *res;
resource_size_t size;
struct phy *generic_phy;
struct usb_phy *phy_dwc3;
const struct phy_drvdata *data;
struct phy_provider *phy_provider;
phy_dwc3 = devm_kzalloc(&pdev->dev, sizeof(*phy_dwc3), GFP_KERNEL);
if (!phy_dwc3)
return -ENOMEM;
data = of_device_get_match_data(&pdev->dev);
phy_dwc3->dev = &pdev->dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -EINVAL;
size = resource_size(res);
phy_dwc3->base = devm_ioremap(phy_dwc3->dev, res->start, size);
if (IS_ERR(phy_dwc3->base)) {
dev_err(phy_dwc3->dev, "failed to map reg\n");
return PTR_ERR(phy_dwc3->base);
}
phy_dwc3->ref_clk = devm_clk_get(phy_dwc3->dev, "ref");
if (IS_ERR(phy_dwc3->ref_clk)) {
dev_dbg(phy_dwc3->dev, "cannot get reference clock\n");
return PTR_ERR(phy_dwc3->ref_clk);
}
clk_set_rate(phy_dwc3->ref_clk, data->clk_rate);
phy_dwc3->xo_clk = devm_clk_get(phy_dwc3->dev, "xo");
if (IS_ERR(phy_dwc3->xo_clk)) {
dev_dbg(phy_dwc3->dev, "cannot get TCXO clock\n");
phy_dwc3->xo_clk = NULL;
}
/* Parse device node to probe HSIO settings */
if (device_property_read_u32(&pdev->dev, "qcom,rx-eq",
&phy_dwc3->rx_eq))
phy_dwc3->rx_eq = SSPHY_RX_EQ_VALUE;
if (device_property_read_u32(&pdev->dev, "qcom,tx-deamp_3_5db",
&phy_dwc3->tx_deamp_3_5db))
phy_dwc3->tx_deamp_3_5db = SSPHY_TX_DEEMPH_3_5DB;
if (device_property_read_u32(&pdev->dev, "qcom,mpll", &phy_dwc3->mpll))
phy_dwc3->mpll = SSPHY_MPLL_VALUE;
generic_phy = devm_phy_create(phy_dwc3->dev, pdev->dev.of_node, &data->ops);
if (IS_ERR(generic_phy))
return PTR_ERR(generic_phy);
phy_set_drvdata(generic_phy, phy_dwc3);
platform_set_drvdata(pdev, phy_dwc3);
phy_provider = devm_of_phy_provider_register(phy_dwc3->dev,
of_phy_simple_xlate);
if (IS_ERR(phy_provider))
return PTR_ERR(phy_provider);
return 0;
}
static struct platform_driver qcom_ipq806x_usb_phy_driver = {
.probe = qcom_ipq806x_usb_phy_probe,
.driver = {
.name = "qcom-ipq806x-usb-phy",
.owner = THIS_MODULE,
.of_match_table = qcom_ipq806x_usb_phy_table,
},
};
module_platform_driver(qcom_ipq806x_usb_phy_driver);
MODULE_ALIAS("platform:phy-qcom-ipq806x-usb");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Andy Gross <agross@codeaurora.org>");
MODULE_AUTHOR("Ivan T. Ivanov <iivanov@mm-sol.com>");
MODULE_DESCRIPTION("DesignWare USB3 QCOM PHY driver");

View File

@ -82,20 +82,34 @@ struct qmp_phy_init_tbl {
* register part of layout ?
* if yes, then offset gives index in the reg-layout
*/
int in_layout;
bool in_layout;
/*
* mask of lanes for which this register is written
* for cases when second lane needs different values
*/
u8 lane_mask;
};
#define QMP_PHY_INIT_CFG(o, v) \
{ \
.offset = o, \
.val = v, \
.lane_mask = 0xff, \
}
#define QMP_PHY_INIT_CFG_L(o, v) \
{ \
.offset = o, \
.val = v, \
.in_layout = 1, \
.in_layout = true, \
.lane_mask = 0xff, \
}
#define QMP_PHY_INIT_CFG_LANE(o, v, l) \
{ \
.offset = o, \
.val = v, \
.lane_mask = l, \
}
/* set of registers with offsets different per-PHY */
@ -185,6 +199,17 @@ static const unsigned int qmp_v4_usb3phy_regs_layout[QPHY_LAYOUT_SIZE] = {
[QPHY_START_CTRL] = 0x44,
[QPHY_PCS_STATUS] = 0x14,
[QPHY_PCS_POWER_DOWN_CONTROL] = 0x40,
[QPHY_PCS_AUTONOMOUS_MODE_CTRL] = 0x308,
[QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR] = 0x314,
};
static const unsigned int qmp_v4_usb3_uniphy_regs_layout[QPHY_LAYOUT_SIZE] = {
[QPHY_SW_RESET] = 0x00,
[QPHY_START_CTRL] = 0x44,
[QPHY_PCS_STATUS] = 0x14,
[QPHY_PCS_POWER_DOWN_CONTROL] = 0x40,
[QPHY_PCS_AUTONOMOUS_MODE_CTRL] = 0x608,
[QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR] = 0x614,
};
static const unsigned int sdm845_ufsphy_regs_layout[QPHY_LAYOUT_SIZE] = {
@ -198,6 +223,81 @@ static const unsigned int sm8150_ufsphy_regs_layout[QPHY_LAYOUT_SIZE] = {
[QPHY_SW_RESET] = QPHY_V4_PCS_UFS_SW_RESET,
};
static const struct qmp_phy_init_tbl ipq8074_usb3_serdes_tbl[] = {
QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x1a),
QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08),
QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x30),
QMP_PHY_INIT_CFG(QSERDES_COM_BG_TRIM, 0x0f),
QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_FASTLOCK_FO_GAIN, 0x0b),
QMP_PHY_INIT_CFG(QSERDES_COM_SVS_MODE_CLK_SEL, 0x01),
QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x00),
QMP_PHY_INIT_CFG(QSERDES_COM_CMN_CONFIG, 0x06),
QMP_PHY_INIT_CFG(QSERDES_COM_PLL_IVCO, 0x0f),
QMP_PHY_INIT_CFG(QSERDES_COM_SYS_CLK_CTRL, 0x06),
/* PLL and Loop filter settings */
QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE0, 0x82),
QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x55),
QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x55),
QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x03),
QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE0, 0x0b),
QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE0, 0x28),
QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80),
QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE0, 0x15),
QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE0, 0x34),
QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE0, 0x00),
QMP_PHY_INIT_CFG(QSERDES_COM_CORE_CLK_EN, 0x00),
QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_CFG, 0x00),
QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_MAP, 0x00),
QMP_PHY_INIT_CFG(QSERDES_COM_BG_TIMER, 0x0a),
/* SSC settings */
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_EN_CENTER, 0x01),
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER1, 0x31),
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER2, 0x01),
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER1, 0x00),
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER2, 0x00),
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE1, 0xde),
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE2, 0x07),
};
static const struct qmp_phy_init_tbl ipq8074_usb3_rx_tbl[] = {
QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN, 0x06),
QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x02),
QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3, 0x4c),
QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4, 0xb8),
QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x77),
QMP_PHY_INIT_CFG(QSERDES_RX_RX_OFFSET_ADAPTOR_CNTRL2, 0x80),
QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_CNTRL, 0x03),
QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x16),
QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_ENABLES, 0x0),
};
static const struct qmp_phy_init_tbl ipq8074_usb3_pcs_tbl[] = {
QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V0, 0x15),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V0, 0x0e),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNTRL2, 0x83),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNTRL1, 0x02),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNT_VAL_L, 0x09),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNT_VAL_H_TOL, 0xa2),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_MAN_CODE, 0x85),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_LOCK_DETECT_CONFIG1, 0xd1),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_LOCK_DETECT_CONFIG2, 0x1f),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_LOCK_DETECT_CONFIG3, 0x47),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_POWER_STATE_CONFIG2, 0x1b),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_RXEQTRAINING_WAIT_TIME, 0x75),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_RXEQTRAINING_RUN_TIME, 0x13),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_LFPS_TX_ECSTART_EQTLOCK, 0x86),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK, 0x04),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_TSYNC_RSYNC_TIME, 0x44),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_P1U2_L, 0xe7),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_P1U2_H, 0x03),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_U3_L, 0x40),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_U3_H, 0x00),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_RX_SIGDET_LVL, 0x88),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V0, 0x17),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V0, 0x0f),
};
static const struct qmp_phy_init_tbl msm8996_pcie_serdes_tbl[] = {
QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x1c),
QMP_PHY_INIT_CFG(QSERDES_COM_CLK_ENABLE1, 0x10),
@ -1399,6 +1499,250 @@ static const struct qmp_phy_init_tbl sm8150_usb3_pcs_tbl[] = {
QMP_PHY_INIT_CFG(QPHY_V4_PCS_USB3_RXEQTRAINING_DFE_TIME_S2, 0x07),
};
static const struct qmp_phy_init_tbl sm8150_usb3_uniphy_serdes_tbl[] = {
QMP_PHY_INIT_CFG(QSERDES_V4_COM_SYSCLK_EN_SEL, 0x1a),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_BIN_VCOCAL_HSCLK_SEL, 0x11),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_HSCLK_SEL, 0x01),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_DEC_START_MODE0, 0x82),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_DIV_FRAC_START1_MODE0, 0xab),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_DIV_FRAC_START2_MODE0, 0xea),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_DIV_FRAC_START3_MODE0, 0x02),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_BIN_VCOCAL_CMP_CODE1_MODE0, 0xca),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_BIN_VCOCAL_CMP_CODE2_MODE0, 0x1e),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_CP_CTRL_MODE0, 0x06),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_PLL_RCTRL_MODE0, 0x16),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_PLL_CCTRL_MODE0, 0x36),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_VCO_TUNE1_MODE0, 0x24),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_LOCK_CMP2_MODE0, 0x34),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_LOCK_CMP1_MODE0, 0x14),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_LOCK_CMP_EN, 0x04),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_SYSCLK_BUF_ENABLE, 0x0a),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_VCO_TUNE2_MODE1, 0x02),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_VCO_TUNE1_MODE1, 0x24),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_CORECLK_DIV_MODE1, 0x08),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_DEC_START_MODE1, 0x82),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_DIV_FRAC_START1_MODE1, 0xab),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_DIV_FRAC_START2_MODE1, 0xea),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_DIV_FRAC_START3_MODE1, 0x02),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_LOCK_CMP2_MODE1, 0x82),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_LOCK_CMP1_MODE1, 0x34),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_CP_CTRL_MODE1, 0x06),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_PLL_RCTRL_MODE1, 0x16),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_PLL_CCTRL_MODE1, 0x36),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_BIN_VCOCAL_CMP_CODE1_MODE1, 0xca),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_BIN_VCOCAL_CMP_CODE2_MODE1, 0x1e),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_CMN_IPTRIM, 0x20),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_SSC_EN_CENTER, 0x01),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_SSC_PER1, 0x31),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_SSC_PER2, 0x01),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_SSC_STEP_SIZE1_MODE1, 0xde),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_SSC_STEP_SIZE2_MODE1, 0x07),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_SSC_STEP_SIZE1_MODE0, 0xde),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_SSC_STEP_SIZE2_MODE0, 0x07),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_VCO_TUNE_MAP, 0x02),
};
static const struct qmp_phy_init_tbl sm8150_usb3_uniphy_tx_tbl[] = {
QMP_PHY_INIT_CFG(QSERDES_V4_TX_RCV_DETECT_LVL_2, 0x12),
QMP_PHY_INIT_CFG(QSERDES_V4_TX_LANE_MODE_1, 0x95),
QMP_PHY_INIT_CFG(QSERDES_V4_TX_PI_QEC_CTRL, 0x40),
QMP_PHY_INIT_CFG(QSERDES_V4_TX_RES_CODE_LANE_OFFSET_TX, 0x05),
};
static const struct qmp_phy_init_tbl sm8150_usb3_uniphy_rx_tbl[] = {
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_00_HIGH4, 0xb8),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_00_HIGH3, 0x7f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_00_HIGH2, 0x37),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_00_HIGH, 0x2f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_00_LOW, 0xef),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_HIGH4, 0xb3),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_HIGH3, 0x0b),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_HIGH2, 0x5c),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_HIGH, 0xdc),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_LOW, 0xdc),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_PI_CONTROLS, 0x99),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SB2_THRESH1, 0x04),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SB2_THRESH2, 0x08),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SB2_GAIN1, 0x05),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SB2_GAIN2, 0x05),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_FASTLOCK_FO_GAIN, 0x2f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_FASTLOCK_COUNT_LOW, 0xff),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_FASTLOCK_COUNT_HIGH, 0x0f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x7f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_FO_GAIN, 0x08),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_VGA_CAL_CNTRL1, 0x54),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_VGA_CAL_CNTRL2, 0x0c),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_GM_CAL, 0x1f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_EQU_ADAPTOR_CNTRL3, 0x4a),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_EQU_ADAPTOR_CNTRL4, 0x0a),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_DFE_EN_TIMER, 0x04),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x47),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_OFFSET_ADAPTOR_CNTRL2, 0x80),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_SIGDET_CNTRL, 0x04),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_SIGDET_DEGLITCH_CNTRL, 0x0e),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_IDAC_TSETTLE_HIGH, 0x00),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_IDAC_TSETTLE_LOW, 0xc0),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_DFE_CTLE_POST_CAL_OFFSET, 0x20),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SO_GAIN, 0x04),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_DCC_CTRL1, 0x0c),
};
static const struct qmp_phy_init_tbl sm8150_usb3_uniphy_pcs_tbl[] = {
QMP_PHY_INIT_CFG(QPHY_V4_PCS_LOCK_DETECT_CONFIG1, 0xd0),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_LOCK_DETECT_CONFIG2, 0x07),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_LOCK_DETECT_CONFIG3, 0x20),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_LOCK_DETECT_CONFIG6, 0x13),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_RCVR_DTCT_DLY_P1U2_L, 0xe7),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_RCVR_DTCT_DLY_P1U2_H, 0x03),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_RX_SIGDET_LVL, 0xaa),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_USB3_UNI_RXEQTRAINING_DFE_TIME_S2, 0x07),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_USB3_UNI_LFPS_DET_HIGH_COUNT_VAL, 0xf8),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_CDR_RESET_TIME, 0x0f),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_ALIGN_DETECT_CONFIG1, 0x88),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_ALIGN_DETECT_CONFIG2, 0x13),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_EQ_CONFIG1, 0x4b),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_EQ_CONFIG5, 0x10),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_REFGEN_REQ_CONFIG1, 0x21),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_PCS_TX_RX_CONFIG, 0x0c),
};
static const struct qmp_phy_init_tbl sm8250_usb3_tx_tbl[] = {
QMP_PHY_INIT_CFG(QSERDES_V4_TX_RES_CODE_LANE_TX, 0x60),
QMP_PHY_INIT_CFG(QSERDES_V4_TX_RES_CODE_LANE_RX, 0x60),
QMP_PHY_INIT_CFG(QSERDES_V4_TX_RES_CODE_LANE_OFFSET_TX, 0x11),
QMP_PHY_INIT_CFG(QSERDES_V4_TX_RES_CODE_LANE_OFFSET_RX, 0x02),
QMP_PHY_INIT_CFG(QSERDES_V4_TX_LANE_MODE_1, 0xd5),
QMP_PHY_INIT_CFG(QSERDES_V4_TX_RCV_DETECT_LVL_2, 0x12),
QMP_PHY_INIT_CFG_LANE(QSERDES_V4_TX_PI_QEC_CTRL, 0x40, 1),
QMP_PHY_INIT_CFG_LANE(QSERDES_V4_TX_PI_QEC_CTRL, 0x54, 2),
};
static const struct qmp_phy_init_tbl sm8250_usb3_rx_tbl[] = {
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SO_GAIN, 0x06),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_FASTLOCK_FO_GAIN, 0x2f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x7f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_FASTLOCK_COUNT_LOW, 0xff),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_FASTLOCK_COUNT_HIGH, 0x0f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_PI_CONTROLS, 0x99),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SB2_THRESH1, 0x04),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SB2_THRESH2, 0x08),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SB2_GAIN1, 0x05),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SB2_GAIN2, 0x05),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_VGA_CAL_CNTRL1, 0x54),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_VGA_CAL_CNTRL2, 0x0c),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_EQU_ADAPTOR_CNTRL3, 0x4a),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_EQU_ADAPTOR_CNTRL4, 0x0a),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_IDAC_TSETTLE_LOW, 0xc0),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_IDAC_TSETTLE_HIGH, 0x00),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x77),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_SIGDET_CNTRL, 0x04),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_SIGDET_DEGLITCH_CNTRL, 0x0e),
QMP_PHY_INIT_CFG_LANE(QSERDES_V4_RX_RX_MODE_00_LOW, 0xff, 1),
QMP_PHY_INIT_CFG_LANE(QSERDES_V4_RX_RX_MODE_00_LOW, 0x7f, 2),
QMP_PHY_INIT_CFG_LANE(QSERDES_V4_RX_RX_MODE_00_HIGH, 0x7f, 1),
QMP_PHY_INIT_CFG_LANE(QSERDES_V4_RX_RX_MODE_00_HIGH, 0xff, 2),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_00_HIGH2, 0x7f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_00_HIGH3, 0x7f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_00_HIGH4, 0x97),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_LOW, 0xdc),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_HIGH, 0xdc),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_HIGH2, 0x5c),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_HIGH3, 0x7b),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_HIGH4, 0xb4),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_DFE_EN_TIMER, 0x04),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_DFE_CTLE_POST_CAL_OFFSET, 0x38),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_AUX_DATA_TCOARSE_TFINE, 0xa0),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_DCC_CTRL1, 0x0c),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_GM_CAL, 0x1f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_VTH_CODE, 0x10),
};
static const struct qmp_phy_init_tbl sm8250_usb3_pcs_tbl[] = {
QMP_PHY_INIT_CFG(QPHY_V4_PCS_LOCK_DETECT_CONFIG1, 0xd0),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_LOCK_DETECT_CONFIG2, 0x07),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_LOCK_DETECT_CONFIG3, 0x20),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_LOCK_DETECT_CONFIG6, 0x13),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_REFGEN_REQ_CONFIG1, 0x21),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_RX_SIGDET_LVL, 0xa9),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_CDR_RESET_TIME, 0x0a),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_ALIGN_DETECT_CONFIG1, 0x88),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_ALIGN_DETECT_CONFIG2, 0x13),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_PCS_TX_RX_CONFIG, 0x0c),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_EQ_CONFIG1, 0x4b),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_EQ_CONFIG5, 0x10),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_USB3_LFPS_DET_HIGH_COUNT_VAL, 0xf8),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_USB3_RXEQTRAINING_DFE_TIME_S2, 0x07),
};
static const struct qmp_phy_init_tbl sm8250_usb3_uniphy_tx_tbl[] = {
QMP_PHY_INIT_CFG(QSERDES_V4_TX_RCV_DETECT_LVL_2, 0x12),
QMP_PHY_INIT_CFG(QSERDES_V4_TX_LANE_MODE_1, 0xd5),
QMP_PHY_INIT_CFG(QSERDES_V4_TX_LANE_MODE_2, 0x82),
QMP_PHY_INIT_CFG(QSERDES_V4_TX_PI_QEC_CTRL, 0x40),
QMP_PHY_INIT_CFG(QSERDES_V4_TX_RES_CODE_LANE_OFFSET_TX, 0x11),
QMP_PHY_INIT_CFG(QSERDES_V4_TX_RES_CODE_LANE_OFFSET_RX, 0x02),
};
static const struct qmp_phy_init_tbl sm8250_usb3_uniphy_rx_tbl[] = {
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_00_HIGH4, 0xb8),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_00_HIGH3, 0xff),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_00_HIGH2, 0xbf),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_00_HIGH, 0x7f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_00_LOW, 0x7f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_HIGH4, 0xb4),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_HIGH3, 0x7b),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_HIGH2, 0x5c),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_HIGH, 0xdc),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_LOW, 0xdc),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_PI_CONTROLS, 0x99),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SB2_THRESH1, 0x04),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SB2_THRESH2, 0x08),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SB2_GAIN1, 0x05),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SB2_GAIN2, 0x05),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_FASTLOCK_FO_GAIN, 0x2f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_FASTLOCK_COUNT_LOW, 0xff),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_FASTLOCK_COUNT_HIGH, 0x0f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x7f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_FO_GAIN, 0x0a),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_VGA_CAL_CNTRL1, 0x54),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_VGA_CAL_CNTRL2, 0x0c),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0f),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_EQU_ADAPTOR_CNTRL3, 0x4a),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_EQU_ADAPTOR_CNTRL4, 0x0a),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_DFE_EN_TIMER, 0x04),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x47),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_OFFSET_ADAPTOR_CNTRL2, 0x80),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_SIGDET_CNTRL, 0x04),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_SIGDET_DEGLITCH_CNTRL, 0x0e),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_IDAC_TSETTLE_HIGH, 0x00),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_IDAC_TSETTLE_LOW, 0xc0),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_DFE_CTLE_POST_CAL_OFFSET, 0x38),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SO_GAIN, 0x06),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_DCC_CTRL1, 0x0c),
QMP_PHY_INIT_CFG(QSERDES_V4_RX_GM_CAL, 0x1f),
};
static const struct qmp_phy_init_tbl sm8250_usb3_uniphy_pcs_tbl[] = {
QMP_PHY_INIT_CFG(QPHY_V4_PCS_LOCK_DETECT_CONFIG1, 0xd0),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_LOCK_DETECT_CONFIG2, 0x07),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_LOCK_DETECT_CONFIG3, 0x20),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_LOCK_DETECT_CONFIG6, 0x13),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_RCVR_DTCT_DLY_P1U2_L, 0xe7),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_RCVR_DTCT_DLY_P1U2_H, 0x03),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_RX_SIGDET_LVL, 0xa9),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_PCS_TX_RX_CONFIG, 0x0c),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_USB3_UNI_RXEQTRAINING_DFE_TIME_S2, 0x07),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_USB3_UNI_LFPS_DET_HIGH_COUNT_VAL, 0xf8),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_CDR_RESET_TIME, 0x0a),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_ALIGN_DETECT_CONFIG1, 0x88),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_ALIGN_DETECT_CONFIG2, 0x13),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_EQ_CONFIG1, 0x4b),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_EQ_CONFIG5, 0x10),
QMP_PHY_INIT_CFG(QPHY_V4_PCS_REFGEN_REQ_CONFIG1, 0x21),
};
/* struct qmp_phy_cfg - per-PHY initialization config */
struct qmp_phy_cfg {
/* phy-type - PCIE/UFS/USB */
@ -1567,6 +1911,11 @@ static const char * const qmp_v4_phy_clk_l[] = {
"aux", "ref_clk_src", "ref", "com_aux",
};
/* the primary usb3 phy on sm8250 doesn't have a ref clock */
static const char * const qmp_v4_sm8250_usbphy_clk_l[] = {
"aux", "ref_clk_src", "com_aux"
};
static const char * const sdm845_ufs_phy_clk_l[] = {
"ref", "ref_aux",
};
@ -1593,6 +1942,30 @@ static const char * const qmp_phy_vreg_l[] = {
"vdda-phy", "vdda-pll",
};
static const struct qmp_phy_cfg ipq8074_usb3phy_cfg = {
.type = PHY_TYPE_USB3,
.nlanes = 1,
.serdes_tbl = ipq8074_usb3_serdes_tbl,
.serdes_tbl_num = ARRAY_SIZE(ipq8074_usb3_serdes_tbl),
.tx_tbl = msm8996_usb3_tx_tbl,
.tx_tbl_num = ARRAY_SIZE(msm8996_usb3_tx_tbl),
.rx_tbl = ipq8074_usb3_rx_tbl,
.rx_tbl_num = ARRAY_SIZE(ipq8074_usb3_rx_tbl),
.pcs_tbl = ipq8074_usb3_pcs_tbl,
.pcs_tbl_num = ARRAY_SIZE(ipq8074_usb3_pcs_tbl),
.clk_list = msm8996_phy_clk_l,
.num_clks = ARRAY_SIZE(msm8996_phy_clk_l),
.reset_list = msm8996_usb3phy_reset_l,
.num_resets = ARRAY_SIZE(msm8996_usb3phy_reset_l),
.vreg_list = qmp_phy_vreg_l,
.num_vregs = ARRAY_SIZE(qmp_phy_vreg_l),
.regs = usb3phy_regs_layout,
.start_ctrl = SERDES_START | PCS_START,
.pwrdn_ctrl = SW_PWRDN,
};
static const struct qmp_phy_cfg msm8996_pciephy_cfg = {
.type = PHY_TYPE_PCIE,
.nlanes = 3,
@ -1986,10 +2359,98 @@ static const struct qmp_phy_cfg sm8150_usb3phy_cfg = {
.is_dual_lane_phy = true,
};
static void qcom_qmp_phy_configure(void __iomem *base,
const unsigned int *regs,
const struct qmp_phy_init_tbl tbl[],
int num)
static const struct qmp_phy_cfg sm8150_usb3_uniphy_cfg = {
.type = PHY_TYPE_USB3,
.nlanes = 1,
.serdes_tbl = sm8150_usb3_uniphy_serdes_tbl,
.serdes_tbl_num = ARRAY_SIZE(sm8150_usb3_uniphy_serdes_tbl),
.tx_tbl = sm8150_usb3_uniphy_tx_tbl,
.tx_tbl_num = ARRAY_SIZE(sm8150_usb3_uniphy_tx_tbl),
.rx_tbl = sm8150_usb3_uniphy_rx_tbl,
.rx_tbl_num = ARRAY_SIZE(sm8150_usb3_uniphy_rx_tbl),
.pcs_tbl = sm8150_usb3_uniphy_pcs_tbl,
.pcs_tbl_num = ARRAY_SIZE(sm8150_usb3_uniphy_pcs_tbl),
.clk_list = qmp_v4_phy_clk_l,
.num_clks = ARRAY_SIZE(qmp_v4_phy_clk_l),
.reset_list = msm8996_usb3phy_reset_l,
.num_resets = ARRAY_SIZE(msm8996_usb3phy_reset_l),
.vreg_list = qmp_phy_vreg_l,
.num_vregs = ARRAY_SIZE(qmp_phy_vreg_l),
.regs = qmp_v4_usb3_uniphy_regs_layout,
.start_ctrl = SERDES_START | PCS_START,
.pwrdn_ctrl = SW_PWRDN,
.has_pwrdn_delay = true,
.pwrdn_delay_min = POWER_DOWN_DELAY_US_MIN,
.pwrdn_delay_max = POWER_DOWN_DELAY_US_MAX,
};
static const struct qmp_phy_cfg sm8250_usb3phy_cfg = {
.type = PHY_TYPE_USB3,
.nlanes = 1,
.serdes_tbl = sm8150_usb3_serdes_tbl,
.serdes_tbl_num = ARRAY_SIZE(sm8150_usb3_serdes_tbl),
.tx_tbl = sm8250_usb3_tx_tbl,
.tx_tbl_num = ARRAY_SIZE(sm8250_usb3_tx_tbl),
.rx_tbl = sm8250_usb3_rx_tbl,
.rx_tbl_num = ARRAY_SIZE(sm8250_usb3_rx_tbl),
.pcs_tbl = sm8250_usb3_pcs_tbl,
.pcs_tbl_num = ARRAY_SIZE(sm8250_usb3_pcs_tbl),
.clk_list = qmp_v4_sm8250_usbphy_clk_l,
.num_clks = ARRAY_SIZE(qmp_v4_sm8250_usbphy_clk_l),
.reset_list = msm8996_usb3phy_reset_l,
.num_resets = ARRAY_SIZE(msm8996_usb3phy_reset_l),
.vreg_list = qmp_phy_vreg_l,
.num_vregs = ARRAY_SIZE(qmp_phy_vreg_l),
.regs = qmp_v4_usb3phy_regs_layout,
.start_ctrl = SERDES_START | PCS_START,
.pwrdn_ctrl = SW_PWRDN,
.has_pwrdn_delay = true,
.pwrdn_delay_min = POWER_DOWN_DELAY_US_MIN,
.pwrdn_delay_max = POWER_DOWN_DELAY_US_MAX,
.has_phy_dp_com_ctrl = true,
.is_dual_lane_phy = true,
};
static const struct qmp_phy_cfg sm8250_usb3_uniphy_cfg = {
.type = PHY_TYPE_USB3,
.nlanes = 1,
.serdes_tbl = sm8150_usb3_uniphy_serdes_tbl,
.serdes_tbl_num = ARRAY_SIZE(sm8150_usb3_uniphy_serdes_tbl),
.tx_tbl = sm8250_usb3_uniphy_tx_tbl,
.tx_tbl_num = ARRAY_SIZE(sm8250_usb3_uniphy_tx_tbl),
.rx_tbl = sm8250_usb3_uniphy_rx_tbl,
.rx_tbl_num = ARRAY_SIZE(sm8250_usb3_uniphy_rx_tbl),
.pcs_tbl = sm8250_usb3_uniphy_pcs_tbl,
.pcs_tbl_num = ARRAY_SIZE(sm8250_usb3_uniphy_pcs_tbl),
.clk_list = qmp_v4_phy_clk_l,
.num_clks = ARRAY_SIZE(qmp_v4_phy_clk_l),
.reset_list = msm8996_usb3phy_reset_l,
.num_resets = ARRAY_SIZE(msm8996_usb3phy_reset_l),
.vreg_list = qmp_phy_vreg_l,
.num_vregs = ARRAY_SIZE(qmp_phy_vreg_l),
.regs = qmp_v4_usb3_uniphy_regs_layout,
.start_ctrl = SERDES_START | PCS_START,
.pwrdn_ctrl = SW_PWRDN,
.has_pwrdn_delay = true,
.pwrdn_delay_min = POWER_DOWN_DELAY_US_MIN,
.pwrdn_delay_max = POWER_DOWN_DELAY_US_MAX,
};
static void qcom_qmp_phy_configure_lane(void __iomem *base,
const unsigned int *regs,
const struct qmp_phy_init_tbl tbl[],
int num,
u8 lane_mask)
{
int i;
const struct qmp_phy_init_tbl *t = tbl;
@ -1998,6 +2459,9 @@ static void qcom_qmp_phy_configure(void __iomem *base,
return;
for (i = 0; i < num; i++, t++) {
if (!(t->lane_mask & lane_mask))
continue;
if (t->in_layout)
writel(t->val, base + regs[t->offset]);
else
@ -2005,6 +2469,14 @@ static void qcom_qmp_phy_configure(void __iomem *base,
}
}
static void qcom_qmp_phy_configure(void __iomem *base,
const unsigned int *regs,
const struct qmp_phy_init_tbl tbl[],
int num)
{
qcom_qmp_phy_configure_lane(base, regs, tbl, num, 0xff);
}
static int qcom_qmp_phy_com_init(struct qmp_phy *qphy)
{
struct qcom_qmp *qmp = qphy->qmp;
@ -2219,16 +2691,18 @@ static int qcom_qmp_phy_enable(struct phy *phy)
}
/* Tx, Rx, and PCS configurations */
qcom_qmp_phy_configure(tx, cfg->regs, cfg->tx_tbl, cfg->tx_tbl_num);
qcom_qmp_phy_configure_lane(tx, cfg->regs,
cfg->tx_tbl, cfg->tx_tbl_num, 1);
/* Configuration for other LANE for USB-DP combo PHY */
if (cfg->is_dual_lane_phy)
qcom_qmp_phy_configure(qphy->tx2, cfg->regs,
cfg->tx_tbl, cfg->tx_tbl_num);
qcom_qmp_phy_configure_lane(qphy->tx2, cfg->regs,
cfg->tx_tbl, cfg->tx_tbl_num, 2);
qcom_qmp_phy_configure(rx, cfg->regs, cfg->rx_tbl, cfg->rx_tbl_num);
qcom_qmp_phy_configure_lane(rx, cfg->regs,
cfg->rx_tbl, cfg->rx_tbl_num, 1);
if (cfg->is_dual_lane_phy)
qcom_qmp_phy_configure(qphy->rx2, cfg->regs,
cfg->rx_tbl, cfg->rx_tbl_num);
qcom_qmp_phy_configure_lane(qphy->rx2, cfg->regs,
cfg->rx_tbl, cfg->rx_tbl_num, 2);
qcom_qmp_phy_configure(pcs, cfg->regs, cfg->pcs_tbl, cfg->pcs_tbl_num);
ret = reset_control_deassert(qmp->ufs_reset);
@ -2699,6 +3173,9 @@ int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id)
static const struct of_device_id qcom_qmp_phy_of_match_table[] = {
{
.compatible = "qcom,ipq8074-qmp-usb3-phy",
.data = &ipq8074_usb3phy_cfg,
}, {
.compatible = "qcom,msm8996-qmp-pcie-phy",
.data = &msm8996_pciephy_cfg,
}, {
@ -2746,6 +3223,15 @@ static const struct of_device_id qcom_qmp_phy_of_match_table[] = {
}, {
.compatible = "qcom,sm8150-qmp-usb3-phy",
.data = &sm8150_usb3phy_cfg,
}, {
.compatible = "qcom,sm8150-qmp-usb3-uni-phy",
.data = &sm8150_usb3_uniphy_cfg,
}, {
.compatible = "qcom,sm8250-qmp-usb3-phy",
.data = &sm8250_usb3phy_cfg,
}, {
.compatible = "qcom,sm8250-qmp-usb3-uni-phy",
.data = &sm8250_usb3_uniphy_cfg,
},
{ },
};

View File

@ -363,7 +363,10 @@
/* Only for QMP V4 PHY - TX registers */
#define QSERDES_V4_TX_RES_CODE_LANE_TX 0x34
#define QSERDES_V4_TX_RES_CODE_LANE_RX 0x38
#define QSERDES_V4_TX_RES_CODE_LANE_OFFSET_TX 0x3c
#define QSERDES_V4_TX_RES_CODE_LANE_OFFSET_RX 0x40
#define QSERDES_V4_TX_LANE_MODE_1 0x84
#define QSERDES_V4_TX_LANE_MODE_2 0x88
#define QSERDES_V4_TX_RCV_DETECT_LVL_2 0x9c
#define QSERDES_V4_TX_PWM_GEAR_1_DIVIDER_BAND0_1 0xd8
#define QSERDES_V4_TX_PWM_GEAR_2_DIVIDER_BAND0_1 0xdC
@ -709,6 +712,10 @@
#define QPHY_V4_PCS_USB3_SIGDET_STARTUP_TIMER_VAL 0x354
#define QPHY_V4_PCS_USB3_TEST_CONTROL 0x358
/* Only for QMP V4 PHY - UNI has 0x300 offset for PCS_USB3 regs */
#define QPHY_V4_PCS_USB3_UNI_LFPS_DET_HIGH_COUNT_VAL 0x618
#define QPHY_V4_PCS_USB3_UNI_RXEQTRAINING_DFE_TIME_S2 0x638
/* Only for QMP V4 PHY - PCS_MISC registers */
#define QPHY_V4_PCS_MISC_TYPEC_CTRL 0x00
#define QPHY_V4_PCS_MISC_TYPEC_PWRDN_CTRL 0x04

View File

@ -810,6 +810,9 @@ static const struct phy_ops qusb2_phy_gen_ops = {
static const struct of_device_id qusb2_phy_of_match_table[] = {
{
.compatible = "qcom,ipq8074-qusb2-phy",
.data = &msm8996_phy_cfg,
}, {
.compatible = "qcom,msm8996-qusb2-phy",
.data = &msm8996_phy_cfg,
}, {

View File

@ -77,6 +77,7 @@ static const char * const qcom_snps_hsphy_vreg_names[] = {
* @phy_reset: phy reset control
* @vregs: regulator supplies bulk data
* @phy_initialized: if PHY has been initialized correctly
* @mode: contains the current mode the PHY is in
*/
struct qcom_snps_hsphy {
struct phy *phy;
@ -88,6 +89,7 @@ struct qcom_snps_hsphy {
struct regulator_bulk_data vregs[SNPS_HS_NUM_VREGS];
bool phy_initialized;
enum phy_mode mode;
};
static inline void qcom_snps_hsphy_write_mask(void __iomem *base, u32 offset,
@ -104,6 +106,72 @@ static inline void qcom_snps_hsphy_write_mask(void __iomem *base, u32 offset,
readl_relaxed(base + offset);
}
static int qcom_snps_hsphy_suspend(struct qcom_snps_hsphy *hsphy)
{
dev_dbg(&hsphy->phy->dev, "Suspend QCOM SNPS PHY\n");
if (hsphy->mode == PHY_MODE_USB_HOST) {
/* Enable auto-resume to meet remote wakeup timing */
qcom_snps_hsphy_write_mask(hsphy->base,
USB2_PHY_USB_PHY_HS_PHY_CTRL2,
USB2_AUTO_RESUME,
USB2_AUTO_RESUME);
usleep_range(500, 1000);
qcom_snps_hsphy_write_mask(hsphy->base,
USB2_PHY_USB_PHY_HS_PHY_CTRL2,
0, USB2_AUTO_RESUME);
}
clk_disable_unprepare(hsphy->cfg_ahb_clk);
return 0;
}
static int qcom_snps_hsphy_resume(struct qcom_snps_hsphy *hsphy)
{
int ret;
dev_dbg(&hsphy->phy->dev, "Resume QCOM SNPS PHY, mode\n");
ret = clk_prepare_enable(hsphy->cfg_ahb_clk);
if (ret) {
dev_err(&hsphy->phy->dev, "failed to enable cfg ahb clock\n");
return ret;
}
return 0;
}
static int __maybe_unused qcom_snps_hsphy_runtime_suspend(struct device *dev)
{
struct qcom_snps_hsphy *hsphy = dev_get_drvdata(dev);
if (!hsphy->phy_initialized)
return 0;
qcom_snps_hsphy_suspend(hsphy);
return 0;
}
static int __maybe_unused qcom_snps_hsphy_runtime_resume(struct device *dev)
{
struct qcom_snps_hsphy *hsphy = dev_get_drvdata(dev);
if (!hsphy->phy_initialized)
return 0;
qcom_snps_hsphy_resume(hsphy);
return 0;
}
static int qcom_snps_hsphy_set_mode(struct phy *phy, enum phy_mode mode,
int submode)
{
struct qcom_snps_hsphy *hsphy = phy_get_drvdata(phy);
hsphy->mode = mode;
return 0;
}
static int qcom_snps_hsphy_init(struct phy *phy)
{
struct qcom_snps_hsphy *hsphy = phy_get_drvdata(phy);
@ -201,6 +269,7 @@ static int qcom_snps_hsphy_exit(struct phy *phy)
static const struct phy_ops qcom_snps_hsphy_gen_ops = {
.init = qcom_snps_hsphy_init,
.exit = qcom_snps_hsphy_exit,
.set_mode = qcom_snps_hsphy_set_mode,
.owner = THIS_MODULE,
};
@ -212,6 +281,11 @@ static const struct of_device_id qcom_snps_hsphy_of_match_table[] = {
};
MODULE_DEVICE_TABLE(of, qcom_snps_hsphy_of_match_table);
static const struct dev_pm_ops qcom_snps_hsphy_pm_ops = {
SET_RUNTIME_PM_OPS(qcom_snps_hsphy_runtime_suspend,
qcom_snps_hsphy_runtime_resume, NULL)
};
static int qcom_snps_hsphy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@ -255,6 +329,14 @@ static int qcom_snps_hsphy_probe(struct platform_device *pdev)
return ret;
}
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
/*
* Prevent runtime pm from being ON by default. Users can enable
* it using power/control in sysfs.
*/
pm_runtime_forbid(dev);
generic_phy = devm_phy_create(dev, NULL, &qcom_snps_hsphy_gen_ops);
if (IS_ERR(generic_phy)) {
ret = PTR_ERR(generic_phy);
@ -269,6 +351,8 @@ static int qcom_snps_hsphy_probe(struct platform_device *pdev)
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
if (!IS_ERR(phy_provider))
dev_dbg(dev, "Registered Qcom-SNPS HS phy\n");
else
pm_runtime_disable(dev);
return PTR_ERR_OR_ZERO(phy_provider);
}
@ -277,6 +361,7 @@ static struct platform_driver qcom_snps_hsphy_driver = {
.probe = qcom_snps_hsphy_probe,
.driver = {
.name = "qcom-snps-hs-femto-v2-phy",
.pm = &qcom_snps_hsphy_pm_ops,
.of_match_table = qcom_snps_hsphy_of_match_table,
},
};

View File

@ -1,131 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
*/
#ifndef UFS_QCOM_PHY_I_H_
#define UFS_QCOM_PHY_I_H_
#include <linux/module.h>
#include <linux/clk.h>
#include <linux/phy/phy.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#define UFS_QCOM_PHY_CAL_ENTRY(reg, val) \
{ \
.reg_offset = reg, \
.cfg_value = val, \
}
#define UFS_QCOM_PHY_NAME_LEN 30
enum {
MASK_SERDES_START = 0x1,
MASK_PCS_READY = 0x1,
};
enum {
OFFSET_SERDES_START = 0x0,
};
struct ufs_qcom_phy_stored_attributes {
u32 att;
u32 value;
};
struct ufs_qcom_phy_calibration {
u32 reg_offset;
u32 cfg_value;
};
struct ufs_qcom_phy_vreg {
const char *name;
struct regulator *reg;
int max_uA;
int min_uV;
int max_uV;
bool enabled;
};
struct ufs_qcom_phy {
struct list_head list;
struct device *dev;
void __iomem *mmio;
void __iomem *dev_ref_clk_ctrl_mmio;
struct clk *tx_iface_clk;
struct clk *rx_iface_clk;
bool is_iface_clk_enabled;
struct clk *ref_clk_src;
struct clk *ref_clk_parent;
struct clk *ref_clk;
bool is_ref_clk_enabled;
bool is_dev_ref_clk_enabled;
struct ufs_qcom_phy_vreg vdda_pll;
struct ufs_qcom_phy_vreg vdda_phy;
struct ufs_qcom_phy_vreg vddp_ref_clk;
unsigned int quirks;
/*
* If UFS link is put into Hibern8 and if UFS PHY analog hardware is
* power collapsed (by clearing UFS_PHY_POWER_DOWN_CONTROL), Hibern8
* exit might fail even after powering on UFS PHY analog hardware.
* Enabling this quirk will help to solve above issue by doing
* custom PHY settings just before PHY analog power collapse.
*/
#define UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE BIT(0)
u8 host_ctrl_rev_major;
u16 host_ctrl_rev_minor;
u16 host_ctrl_rev_step;
char name[UFS_QCOM_PHY_NAME_LEN];
struct ufs_qcom_phy_calibration *cached_regs;
int cached_regs_table_size;
struct ufs_qcom_phy_specific_ops *phy_spec_ops;
enum phy_mode mode;
struct reset_control *ufs_reset;
};
/**
* struct ufs_qcom_phy_specific_ops - set of pointers to functions which have a
* specific implementation per phy. Each UFS phy, should implement
* those functions according to its spec and requirements
* @start_serdes: pointer to a function that starts the serdes
* @is_physical_coding_sublayer_ready: pointer to a function that
* checks pcs readiness. returns 0 for success and non-zero for error.
* @set_tx_lane_enable: pointer to a function that enable tx lanes
* @power_control: pointer to a function that controls analog rail of phy
* and writes to QSERDES_RX_SIGDET_CNTRL attribute
*/
struct ufs_qcom_phy_specific_ops {
int (*calibrate)(struct ufs_qcom_phy *ufs_qcom_phy, bool is_rate_B);
void (*start_serdes)(struct ufs_qcom_phy *phy);
int (*is_physical_coding_sublayer_ready)(struct ufs_qcom_phy *phy);
void (*set_tx_lane_enable)(struct ufs_qcom_phy *phy, u32 val);
void (*power_control)(struct ufs_qcom_phy *phy, bool val);
};
struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy);
int ufs_qcom_phy_power_on(struct phy *generic_phy);
int ufs_qcom_phy_power_off(struct phy *generic_phy);
int ufs_qcom_phy_init_clks(struct ufs_qcom_phy *phy_common);
int ufs_qcom_phy_init_vregulators(struct ufs_qcom_phy *phy_common);
int ufs_qcom_phy_remove(struct phy *generic_phy,
struct ufs_qcom_phy *ufs_qcom_phy);
struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
struct ufs_qcom_phy *common_cfg,
const struct phy_ops *ufs_qcom_phy_gen_ops,
struct ufs_qcom_phy_specific_ops *phy_spec_ops);
int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
struct ufs_qcom_phy_calibration *tbl_A, int tbl_size_A,
struct ufs_qcom_phy_calibration *tbl_B, int tbl_size_B,
bool is_rate_B);
#endif

View File

@ -1,172 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
*/
#include "phy-qcom-ufs-qmp-14nm.h"
#define UFS_PHY_NAME "ufs_phy_qmp_14nm"
#define UFS_PHY_VDDA_PHY_UV (925000)
static
int ufs_qcom_phy_qmp_14nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
bool is_rate_B)
{
int tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A);
int tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
int err;
err = ufs_qcom_phy_calibrate(ufs_qcom_phy, phy_cal_table_rate_A,
tbl_size_A, phy_cal_table_rate_B, tbl_size_B, is_rate_B);
if (err)
dev_err(ufs_qcom_phy->dev,
"%s: ufs_qcom_phy_calibrate() failed %d\n",
__func__, err);
return err;
}
static
void ufs_qcom_phy_qmp_14nm_advertise_quirks(struct ufs_qcom_phy *phy_common)
{
phy_common->quirks =
UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
}
static
int ufs_qcom_phy_qmp_14nm_set_mode(struct phy *generic_phy,
enum phy_mode mode, int submode)
{
struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
phy_common->mode = PHY_MODE_INVALID;
if (mode > 0)
phy_common->mode = mode;
return 0;
}
static
void ufs_qcom_phy_qmp_14nm_power_control(struct ufs_qcom_phy *phy, bool val)
{
writel_relaxed(val ? 0x1 : 0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
/*
* Before any transactions involving PHY, ensure PHY knows
* that it's analog rail is powered ON (or OFF).
*/
mb();
}
static inline
void ufs_qcom_phy_qmp_14nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val)
{
/*
* 14nm PHY does not have TX_LANE_ENABLE register.
* Implement this function so as not to propagate error to caller.
*/
}
static inline void ufs_qcom_phy_qmp_14nm_start_serdes(struct ufs_qcom_phy *phy)
{
u32 tmp;
tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
tmp &= ~MASK_SERDES_START;
tmp |= (1 << OFFSET_SERDES_START);
writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
/* Ensure register value is committed */
mb();
}
static int ufs_qcom_phy_qmp_14nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
{
int err = 0;
u32 val;
err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS,
val, (val & MASK_PCS_READY), 10, 1000000);
if (err)
dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n",
__func__, err);
return err;
}
static const struct phy_ops ufs_qcom_phy_qmp_14nm_phy_ops = {
.power_on = ufs_qcom_phy_power_on,
.power_off = ufs_qcom_phy_power_off,
.set_mode = ufs_qcom_phy_qmp_14nm_set_mode,
.owner = THIS_MODULE,
};
static struct ufs_qcom_phy_specific_ops phy_14nm_ops = {
.calibrate = ufs_qcom_phy_qmp_14nm_phy_calibrate,
.start_serdes = ufs_qcom_phy_qmp_14nm_start_serdes,
.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_14nm_is_pcs_ready,
.set_tx_lane_enable = ufs_qcom_phy_qmp_14nm_set_tx_lane_enable,
.power_control = ufs_qcom_phy_qmp_14nm_power_control,
};
static int ufs_qcom_phy_qmp_14nm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct phy *generic_phy;
struct ufs_qcom_phy_qmp_14nm *phy;
struct ufs_qcom_phy *phy_common;
int err = 0;
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy) {
err = -ENOMEM;
goto out;
}
phy_common = &phy->common_cfg;
generic_phy = ufs_qcom_phy_generic_probe(pdev, phy_common,
&ufs_qcom_phy_qmp_14nm_phy_ops, &phy_14nm_ops);
if (!generic_phy) {
err = -EIO;
goto out;
}
err = ufs_qcom_phy_init_clks(phy_common);
if (err)
goto out;
err = ufs_qcom_phy_init_vregulators(phy_common);
if (err)
goto out;
phy_common->vdda_phy.max_uV = UFS_PHY_VDDA_PHY_UV;
phy_common->vdda_phy.min_uV = UFS_PHY_VDDA_PHY_UV;
ufs_qcom_phy_qmp_14nm_advertise_quirks(phy_common);
phy_set_drvdata(generic_phy, phy);
strlcpy(phy_common->name, UFS_PHY_NAME, sizeof(phy_common->name));
out:
return err;
}
static const struct of_device_id ufs_qcom_phy_qmp_14nm_of_match[] = {
{.compatible = "qcom,ufs-phy-qmp-14nm"},
{.compatible = "qcom,msm8996-ufs-phy-qmp-14nm"},
{},
};
MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_14nm_of_match);
static struct platform_driver ufs_qcom_phy_qmp_14nm_driver = {
.probe = ufs_qcom_phy_qmp_14nm_probe,
.driver = {
.of_match_table = ufs_qcom_phy_qmp_14nm_of_match,
.name = "ufs_qcom_phy_qmp_14nm",
},
};
module_platform_driver(ufs_qcom_phy_qmp_14nm_driver);
MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 14nm");
MODULE_LICENSE("GPL v2");

View File

@ -1,168 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
*/
#ifndef UFS_QCOM_PHY_QMP_14NM_H_
#define UFS_QCOM_PHY_QMP_14NM_H_
#include "phy-qcom-ufs-i.h"
/* QCOM UFS PHY control registers */
#define COM_OFF(x) (0x000 + x)
#define PHY_OFF(x) (0xC00 + x)
#define TX_OFF(n, x) (0x400 + (0x400 * n) + x)
#define RX_OFF(n, x) (0x600 + (0x400 * n) + x)
/* UFS PHY QSERDES COM registers */
#define QSERDES_COM_BG_TIMER COM_OFF(0x0C)
#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN COM_OFF(0x34)
#define QSERDES_COM_SYS_CLK_CTRL COM_OFF(0x3C)
#define QSERDES_COM_LOCK_CMP1_MODE0 COM_OFF(0x4C)
#define QSERDES_COM_LOCK_CMP2_MODE0 COM_OFF(0x50)
#define QSERDES_COM_LOCK_CMP3_MODE0 COM_OFF(0x54)
#define QSERDES_COM_LOCK_CMP1_MODE1 COM_OFF(0x58)
#define QSERDES_COM_LOCK_CMP2_MODE1 COM_OFF(0x5C)
#define QSERDES_COM_LOCK_CMP3_MODE1 COM_OFF(0x60)
#define QSERDES_COM_CP_CTRL_MODE0 COM_OFF(0x78)
#define QSERDES_COM_CP_CTRL_MODE1 COM_OFF(0x7C)
#define QSERDES_COM_PLL_RCTRL_MODE0 COM_OFF(0x84)
#define QSERDES_COM_PLL_RCTRL_MODE1 COM_OFF(0x88)
#define QSERDES_COM_PLL_CCTRL_MODE0 COM_OFF(0x90)
#define QSERDES_COM_PLL_CCTRL_MODE1 COM_OFF(0x94)
#define QSERDES_COM_SYSCLK_EN_SEL COM_OFF(0xAC)
#define QSERDES_COM_RESETSM_CNTRL COM_OFF(0xB4)
#define QSERDES_COM_LOCK_CMP_EN COM_OFF(0xC8)
#define QSERDES_COM_LOCK_CMP_CFG COM_OFF(0xCC)
#define QSERDES_COM_DEC_START_MODE0 COM_OFF(0xD0)
#define QSERDES_COM_DEC_START_MODE1 COM_OFF(0xD4)
#define QSERDES_COM_DIV_FRAC_START1_MODE0 COM_OFF(0xDC)
#define QSERDES_COM_DIV_FRAC_START2_MODE0 COM_OFF(0xE0)
#define QSERDES_COM_DIV_FRAC_START3_MODE0 COM_OFF(0xE4)
#define QSERDES_COM_DIV_FRAC_START1_MODE1 COM_OFF(0xE8)
#define QSERDES_COM_DIV_FRAC_START2_MODE1 COM_OFF(0xEC)
#define QSERDES_COM_DIV_FRAC_START3_MODE1 COM_OFF(0xF0)
#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 COM_OFF(0x108)
#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 COM_OFF(0x10C)
#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1 COM_OFF(0x110)
#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1 COM_OFF(0x114)
#define QSERDES_COM_VCO_TUNE_CTRL COM_OFF(0x124)
#define QSERDES_COM_VCO_TUNE_MAP COM_OFF(0x128)
#define QSERDES_COM_VCO_TUNE1_MODE0 COM_OFF(0x12C)
#define QSERDES_COM_VCO_TUNE2_MODE0 COM_OFF(0x130)
#define QSERDES_COM_VCO_TUNE1_MODE1 COM_OFF(0x134)
#define QSERDES_COM_VCO_TUNE2_MODE1 COM_OFF(0x138)
#define QSERDES_COM_VCO_TUNE_TIMER1 COM_OFF(0x144)
#define QSERDES_COM_VCO_TUNE_TIMER2 COM_OFF(0x148)
#define QSERDES_COM_CLK_SELECT COM_OFF(0x174)
#define QSERDES_COM_HSCLK_SEL COM_OFF(0x178)
#define QSERDES_COM_CORECLK_DIV COM_OFF(0x184)
#define QSERDES_COM_CORE_CLK_EN COM_OFF(0x18C)
#define QSERDES_COM_CMN_CONFIG COM_OFF(0x194)
#define QSERDES_COM_SVS_MODE_CLK_SEL COM_OFF(0x19C)
#define QSERDES_COM_CORECLK_DIV_MODE1 COM_OFF(0x1BC)
/* UFS PHY registers */
#define UFS_PHY_PHY_START PHY_OFF(0x00)
#define UFS_PHY_POWER_DOWN_CONTROL PHY_OFF(0x04)
#define UFS_PHY_PCS_READY_STATUS PHY_OFF(0x168)
/* UFS PHY TX registers */
#define QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN TX_OFF(0, 0x68)
#define QSERDES_TX_LANE_MODE TX_OFF(0, 0x94)
/* UFS PHY RX registers */
#define QSERDES_RX_UCDR_FASTLOCK_FO_GAIN RX_OFF(0, 0x40)
#define QSERDES_RX_RX_TERM_BW RX_OFF(0, 0x90)
#define QSERDES_RX_RX_EQ_GAIN1_LSB RX_OFF(0, 0xC4)
#define QSERDES_RX_RX_EQ_GAIN1_MSB RX_OFF(0, 0xC8)
#define QSERDES_RX_RX_EQ_GAIN2_LSB RX_OFF(0, 0xCC)
#define QSERDES_RX_RX_EQ_GAIN2_MSB RX_OFF(0, 0xD0)
#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2 RX_OFF(0, 0xD8)
#define QSERDES_RX_SIGDET_CNTRL RX_OFF(0, 0x114)
#define QSERDES_RX_SIGDET_LVL RX_OFF(0, 0x118)
#define QSERDES_RX_SIGDET_DEGLITCH_CNTRL RX_OFF(0, 0x11C)
#define QSERDES_RX_RX_INTERFACE_MODE RX_OFF(0, 0x12C)
/*
* This structure represents the 14nm specific phy.
* common_cfg MUST remain the first field in this structure
* in case extra fields are added. This way, when calling
* get_ufs_qcom_phy() of generic phy, we can extract the
* common phy structure (struct ufs_qcom_phy) out of it
* regardless of the relevant specific phy.
*/
struct ufs_qcom_phy_qmp_14nm {
struct ufs_qcom_phy common_cfg;
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_A[] = {
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CMN_CONFIG, 0x0e),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL, 0xd7),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CLK_SELECT, 0x30),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYS_CLK_CTRL, 0x06),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BG_TIMER, 0x0a),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_HSCLK_SEL, 0x05),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV, 0x0a),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV_MODE1, 0x0a),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_EN, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_CTRL, 0x10),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x20),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORE_CLK_EN, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_CFG, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_TIMER1, 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_TIMER2, 0x3f),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x14),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SVS_MODE_CLK_SEL, 0x05),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE0, 0x82),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE0, 0x0b),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE0, 0x28),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE0, 0x28),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE0, 0x02),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE0, 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE0, 0x0c),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP3_MODE0, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE1, 0x98),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1_MODE1, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2_MODE1, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3_MODE1, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE1, 0x0b),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE1, 0x16),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE1, 0x28),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE1, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE1, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE1, 0xd6),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE1, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE1, 0x32),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE1, 0x0f),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP3_MODE1, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN, 0x45),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE, 0x02),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_LVL, 0x24),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_CNTRL, 0x02),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_INTERFACE_MODE, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x18),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_UCDR_FASTLOCK_FO_GAIN, 0x0B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_TERM_BW, 0x5B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB, 0xFF),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB, 0x3F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB, 0xFF),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB, 0x0F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0E),
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x54),
};
#endif

View File

@ -1,226 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
*/
#include "phy-qcom-ufs-qmp-20nm.h"
#define UFS_PHY_NAME "ufs_phy_qmp_20nm"
static
int ufs_qcom_phy_qmp_20nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
bool is_rate_B)
{
struct ufs_qcom_phy_calibration *tbl_A, *tbl_B;
int tbl_size_A, tbl_size_B;
u8 major = ufs_qcom_phy->host_ctrl_rev_major;
u16 minor = ufs_qcom_phy->host_ctrl_rev_minor;
u16 step = ufs_qcom_phy->host_ctrl_rev_step;
int err;
if ((major == 0x1) && (minor == 0x002) && (step == 0x0000)) {
tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_2_0);
tbl_A = phy_cal_table_rate_A_1_2_0;
} else if ((major == 0x1) && (minor == 0x003) && (step == 0x0000)) {
tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_3_0);
tbl_A = phy_cal_table_rate_A_1_3_0;
} else {
dev_err(ufs_qcom_phy->dev, "%s: Unknown UFS-PHY version, no calibration values\n",
__func__);
err = -ENODEV;
goto out;
}
tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
tbl_B = phy_cal_table_rate_B;
err = ufs_qcom_phy_calibrate(ufs_qcom_phy, tbl_A, tbl_size_A,
tbl_B, tbl_size_B, is_rate_B);
if (err)
dev_err(ufs_qcom_phy->dev, "%s: ufs_qcom_phy_calibrate() failed %d\n",
__func__, err);
out:
return err;
}
static
void ufs_qcom_phy_qmp_20nm_advertise_quirks(struct ufs_qcom_phy *phy_common)
{
phy_common->quirks =
UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
}
static
int ufs_qcom_phy_qmp_20nm_set_mode(struct phy *generic_phy,
enum phy_mode mode, int submode)
{
struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
phy_common->mode = PHY_MODE_INVALID;
if (mode > 0)
phy_common->mode = mode;
return 0;
}
static
void ufs_qcom_phy_qmp_20nm_power_control(struct ufs_qcom_phy *phy, bool val)
{
bool hibern8_exit_after_pwr_collapse = phy->quirks &
UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
if (val) {
writel_relaxed(0x1, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
/*
* Before any transactions involving PHY, ensure PHY knows
* that it's analog rail is powered ON.
*/
mb();
if (hibern8_exit_after_pwr_collapse) {
/*
* Give atleast 1us delay after restoring PHY analog
* power.
*/
usleep_range(1, 2);
writel_relaxed(0x0A, phy->mmio +
QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
writel_relaxed(0x08, phy->mmio +
QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
/*
* Make sure workaround is deactivated before proceeding
* with normal PHY operations.
*/
mb();
}
} else {
if (hibern8_exit_after_pwr_collapse) {
writel_relaxed(0x0A, phy->mmio +
QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
writel_relaxed(0x02, phy->mmio +
QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
/*
* Make sure that above workaround is activated before
* PHY analog power collapse.
*/
mb();
}
writel_relaxed(0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
/*
* ensure that PHY knows its PHY analog rail is going
* to be powered down
*/
mb();
}
}
static
void ufs_qcom_phy_qmp_20nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val)
{
writel_relaxed(val & UFS_PHY_TX_LANE_ENABLE_MASK,
phy->mmio + UFS_PHY_TX_LANE_ENABLE);
mb();
}
static inline void ufs_qcom_phy_qmp_20nm_start_serdes(struct ufs_qcom_phy *phy)
{
u32 tmp;
tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
tmp &= ~MASK_SERDES_START;
tmp |= (1 << OFFSET_SERDES_START);
writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
mb();
}
static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
{
int err = 0;
u32 val;
err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS,
val, (val & MASK_PCS_READY), 10, 1000000);
if (err)
dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n",
__func__, err);
return err;
}
static const struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
.power_on = ufs_qcom_phy_power_on,
.power_off = ufs_qcom_phy_power_off,
.set_mode = ufs_qcom_phy_qmp_20nm_set_mode,
.owner = THIS_MODULE,
};
static struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
.calibrate = ufs_qcom_phy_qmp_20nm_phy_calibrate,
.start_serdes = ufs_qcom_phy_qmp_20nm_start_serdes,
.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_20nm_is_pcs_ready,
.set_tx_lane_enable = ufs_qcom_phy_qmp_20nm_set_tx_lane_enable,
.power_control = ufs_qcom_phy_qmp_20nm_power_control,
};
static int ufs_qcom_phy_qmp_20nm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct phy *generic_phy;
struct ufs_qcom_phy_qmp_20nm *phy;
struct ufs_qcom_phy *phy_common;
int err = 0;
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy) {
err = -ENOMEM;
goto out;
}
phy_common = &phy->common_cfg;
generic_phy = ufs_qcom_phy_generic_probe(pdev, phy_common,
&ufs_qcom_phy_qmp_20nm_phy_ops, &phy_20nm_ops);
if (!generic_phy) {
err = -EIO;
goto out;
}
err = ufs_qcom_phy_init_clks(phy_common);
if (err)
goto out;
err = ufs_qcom_phy_init_vregulators(phy_common);
if (err)
goto out;
ufs_qcom_phy_qmp_20nm_advertise_quirks(phy_common);
phy_set_drvdata(generic_phy, phy);
strlcpy(phy_common->name, UFS_PHY_NAME, sizeof(phy_common->name));
out:
return err;
}
static const struct of_device_id ufs_qcom_phy_qmp_20nm_of_match[] = {
{.compatible = "qcom,ufs-phy-qmp-20nm"},
{},
};
MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_20nm_of_match);
static struct platform_driver ufs_qcom_phy_qmp_20nm_driver = {
.probe = ufs_qcom_phy_qmp_20nm_probe,
.driver = {
.of_match_table = ufs_qcom_phy_qmp_20nm_of_match,
.name = "ufs_qcom_phy_qmp_20nm",
},
};
module_platform_driver(ufs_qcom_phy_qmp_20nm_driver);
MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 20nm");
MODULE_LICENSE("GPL v2");

View File

@ -1,226 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
*/
#ifndef UFS_QCOM_PHY_QMP_20NM_H_
#define UFS_QCOM_PHY_QMP_20NM_H_
#include "phy-qcom-ufs-i.h"
/* QCOM UFS PHY control registers */
#define COM_OFF(x) (0x000 + x)
#define PHY_OFF(x) (0xC00 + x)
#define TX_OFF(n, x) (0x400 + (0x400 * n) + x)
#define RX_OFF(n, x) (0x600 + (0x400 * n) + x)
/* UFS PHY PLL block registers */
#define QSERDES_COM_SYS_CLK_CTRL COM_OFF(0x0)
#define QSERDES_COM_PLL_VCOTAIL_EN COM_OFF(0x04)
#define QSERDES_COM_PLL_CNTRL COM_OFF(0x14)
#define QSERDES_COM_PLL_IP_SETI COM_OFF(0x24)
#define QSERDES_COM_CORE_CLK_IN_SYNC_SEL COM_OFF(0x28)
#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN COM_OFF(0x30)
#define QSERDES_COM_PLL_CP_SETI COM_OFF(0x34)
#define QSERDES_COM_PLL_IP_SETP COM_OFF(0x38)
#define QSERDES_COM_PLL_CP_SETP COM_OFF(0x3C)
#define QSERDES_COM_SYSCLK_EN_SEL_TXBAND COM_OFF(0x48)
#define QSERDES_COM_RESETSM_CNTRL COM_OFF(0x4C)
#define QSERDES_COM_RESETSM_CNTRL2 COM_OFF(0x50)
#define QSERDES_COM_PLLLOCK_CMP1 COM_OFF(0x90)
#define QSERDES_COM_PLLLOCK_CMP2 COM_OFF(0x94)
#define QSERDES_COM_PLLLOCK_CMP3 COM_OFF(0x98)
#define QSERDES_COM_PLLLOCK_CMP_EN COM_OFF(0x9C)
#define QSERDES_COM_BGTC COM_OFF(0xA0)
#define QSERDES_COM_DEC_START1 COM_OFF(0xAC)
#define QSERDES_COM_PLL_AMP_OS COM_OFF(0xB0)
#define QSERDES_COM_RES_CODE_UP_OFFSET COM_OFF(0xD8)
#define QSERDES_COM_RES_CODE_DN_OFFSET COM_OFF(0xDC)
#define QSERDES_COM_DIV_FRAC_START1 COM_OFF(0x100)
#define QSERDES_COM_DIV_FRAC_START2 COM_OFF(0x104)
#define QSERDES_COM_DIV_FRAC_START3 COM_OFF(0x108)
#define QSERDES_COM_DEC_START2 COM_OFF(0x10C)
#define QSERDES_COM_PLL_RXTXEPCLK_EN COM_OFF(0x110)
#define QSERDES_COM_PLL_CRCTRL COM_OFF(0x114)
#define QSERDES_COM_PLL_CLKEPDIV COM_OFF(0x118)
/* TX LANE n (0, 1) registers */
#define QSERDES_TX_EMP_POST1_LVL(n) TX_OFF(n, 0x08)
#define QSERDES_TX_DRV_LVL(n) TX_OFF(n, 0x0C)
#define QSERDES_TX_LANE_MODE(n) TX_OFF(n, 0x54)
/* RX LANE n (0, 1) registers */
#define QSERDES_RX_CDR_CONTROL1(n) RX_OFF(n, 0x0)
#define QSERDES_RX_CDR_CONTROL_HALF(n) RX_OFF(n, 0x8)
#define QSERDES_RX_RX_EQ_GAIN1_LSB(n) RX_OFF(n, 0xA8)
#define QSERDES_RX_RX_EQ_GAIN1_MSB(n) RX_OFF(n, 0xAC)
#define QSERDES_RX_RX_EQ_GAIN2_LSB(n) RX_OFF(n, 0xB0)
#define QSERDES_RX_RX_EQ_GAIN2_MSB(n) RX_OFF(n, 0xB4)
#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(n) RX_OFF(n, 0xBC)
#define QSERDES_RX_CDR_CONTROL_QUARTER(n) RX_OFF(n, 0xC)
#define QSERDES_RX_SIGDET_CNTRL(n) RX_OFF(n, 0x100)
/* UFS PHY registers */
#define UFS_PHY_PHY_START PHY_OFF(0x00)
#define UFS_PHY_POWER_DOWN_CONTROL PHY_OFF(0x4)
#define UFS_PHY_TX_LANE_ENABLE PHY_OFF(0x44)
#define UFS_PHY_PWM_G1_CLK_DIVIDER PHY_OFF(0x08)
#define UFS_PHY_PWM_G2_CLK_DIVIDER PHY_OFF(0x0C)
#define UFS_PHY_PWM_G3_CLK_DIVIDER PHY_OFF(0x10)
#define UFS_PHY_PWM_G4_CLK_DIVIDER PHY_OFF(0x14)
#define UFS_PHY_CORECLK_PWM_G1_CLK_DIVIDER PHY_OFF(0x34)
#define UFS_PHY_CORECLK_PWM_G2_CLK_DIVIDER PHY_OFF(0x38)
#define UFS_PHY_CORECLK_PWM_G3_CLK_DIVIDER PHY_OFF(0x3C)
#define UFS_PHY_CORECLK_PWM_G4_CLK_DIVIDER PHY_OFF(0x40)
#define UFS_PHY_OMC_STATUS_RDVAL PHY_OFF(0x68)
#define UFS_PHY_LINE_RESET_TIME PHY_OFF(0x28)
#define UFS_PHY_LINE_RESET_GRANULARITY PHY_OFF(0x2C)
#define UFS_PHY_TSYNC_RSYNC_CNTL PHY_OFF(0x48)
#define UFS_PHY_PLL_CNTL PHY_OFF(0x50)
#define UFS_PHY_TX_LARGE_AMP_DRV_LVL PHY_OFF(0x54)
#define UFS_PHY_TX_SMALL_AMP_DRV_LVL PHY_OFF(0x5C)
#define UFS_PHY_TX_LARGE_AMP_POST_EMP_LVL PHY_OFF(0x58)
#define UFS_PHY_TX_SMALL_AMP_POST_EMP_LVL PHY_OFF(0x60)
#define UFS_PHY_CFG_CHANGE_CNT_VAL PHY_OFF(0x64)
#define UFS_PHY_RX_SYNC_WAIT_TIME PHY_OFF(0x6C)
#define UFS_PHY_TX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xB4)
#define UFS_PHY_RX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xE0)
#define UFS_PHY_TX_MIN_STALL_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xB8)
#define UFS_PHY_RX_MIN_STALL_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xE4)
#define UFS_PHY_TX_MIN_SAVE_CONFIG_TIME_CAPABILITY PHY_OFF(0xBC)
#define UFS_PHY_RX_MIN_SAVE_CONFIG_TIME_CAPABILITY PHY_OFF(0xE8)
#define UFS_PHY_RX_PWM_BURST_CLOSURE_LENGTH_CAPABILITY PHY_OFF(0xFC)
#define UFS_PHY_RX_MIN_ACTIVATETIME_CAPABILITY PHY_OFF(0x100)
#define UFS_PHY_RX_SIGDET_CTRL3 PHY_OFF(0x14c)
#define UFS_PHY_RMMI_ATTR_CTRL PHY_OFF(0x160)
#define UFS_PHY_RMMI_RX_CFGUPDT_L1 (1 << 7)
#define UFS_PHY_RMMI_TX_CFGUPDT_L1 (1 << 6)
#define UFS_PHY_RMMI_CFGWR_L1 (1 << 5)
#define UFS_PHY_RMMI_CFGRD_L1 (1 << 4)
#define UFS_PHY_RMMI_RX_CFGUPDT_L0 (1 << 3)
#define UFS_PHY_RMMI_TX_CFGUPDT_L0 (1 << 2)
#define UFS_PHY_RMMI_CFGWR_L0 (1 << 1)
#define UFS_PHY_RMMI_CFGRD_L0 (1 << 0)
#define UFS_PHY_RMMI_ATTRID PHY_OFF(0x164)
#define UFS_PHY_RMMI_ATTRWRVAL PHY_OFF(0x168)
#define UFS_PHY_RMMI_ATTRRDVAL_L0_STATUS PHY_OFF(0x16C)
#define UFS_PHY_RMMI_ATTRRDVAL_L1_STATUS PHY_OFF(0x170)
#define UFS_PHY_PCS_READY_STATUS PHY_OFF(0x174)
#define UFS_PHY_TX_LANE_ENABLE_MASK 0x3
/*
* This structure represents the 20nm specific phy.
* common_cfg MUST remain the first field in this structure
* in case extra fields are added. This way, when calling
* get_ufs_qcom_phy() of generic phy, we can extract the
* common phy structure (struct ufs_qcom_phy) out of it
* regardless of the relevant specific phy.
*/
struct ufs_qcom_phy_qmp_20nm {
struct ufs_qcom_phy common_cfg;
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_2_0[] = {
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x3f),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x1b),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x0f),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(0), 0x2F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(0), 0x20),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(1), 0x2F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(1), 0x20),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3),
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_3_0[] = {
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x2b),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x38),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x3c),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_UP_OFFSET, 0x02),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_DN_OFFSET, 0x02),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CNTRL, 0x40),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3),
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x98),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0x65),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x1e),
};
#endif

View File

@ -1,648 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
*/
#include "phy-qcom-ufs-i.h"
#define MAX_PROP_NAME 32
#define VDDA_PHY_MIN_UV 1000000
#define VDDA_PHY_MAX_UV 1000000
#define VDDA_PLL_MIN_UV 1800000
#define VDDA_PLL_MAX_UV 1800000
#define VDDP_REF_CLK_MIN_UV 1200000
#define VDDP_REF_CLK_MAX_UV 1200000
int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
struct ufs_qcom_phy_calibration *tbl_A,
int tbl_size_A,
struct ufs_qcom_phy_calibration *tbl_B,
int tbl_size_B, bool is_rate_B)
{
int i;
int ret = 0;
if (!tbl_A) {
dev_err(ufs_qcom_phy->dev, "%s: tbl_A is NULL", __func__);
ret = EINVAL;
goto out;
}
for (i = 0; i < tbl_size_A; i++)
writel_relaxed(tbl_A[i].cfg_value,
ufs_qcom_phy->mmio + tbl_A[i].reg_offset);
/*
* In case we would like to work in rate B, we need
* to override a registers that were configured in rate A table
* with registers of rate B table.
* table.
*/
if (is_rate_B) {
if (!tbl_B) {
dev_err(ufs_qcom_phy->dev, "%s: tbl_B is NULL",
__func__);
ret = EINVAL;
goto out;
}
for (i = 0; i < tbl_size_B; i++)
writel_relaxed(tbl_B[i].cfg_value,
ufs_qcom_phy->mmio + tbl_B[i].reg_offset);
}
/* flush buffered writes */
mb();
out:
return ret;
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_calibrate);
/*
* This assumes the embedded phy structure inside generic_phy is of type
* struct ufs_qcom_phy. In order to function properly it's crucial
* to keep the embedded struct "struct ufs_qcom_phy common_cfg"
* as the first inside generic_phy.
*/
struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy)
{
return (struct ufs_qcom_phy *)phy_get_drvdata(generic_phy);
}
EXPORT_SYMBOL_GPL(get_ufs_qcom_phy);
static
int ufs_qcom_phy_base_init(struct platform_device *pdev,
struct ufs_qcom_phy *phy_common)
{
struct device *dev = &pdev->dev;
struct resource *res;
int err = 0;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_mem");
phy_common->mmio = devm_ioremap_resource(dev, res);
if (IS_ERR((void const *)phy_common->mmio)) {
err = PTR_ERR((void const *)phy_common->mmio);
phy_common->mmio = NULL;
dev_err(dev, "%s: ioremap for phy_mem resource failed %d\n",
__func__, err);
return err;
}
/* "dev_ref_clk_ctrl_mem" is optional resource */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"dev_ref_clk_ctrl_mem");
phy_common->dev_ref_clk_ctrl_mmio = devm_ioremap_resource(dev, res);
if (IS_ERR((void const *)phy_common->dev_ref_clk_ctrl_mmio))
phy_common->dev_ref_clk_ctrl_mmio = NULL;
return 0;
}
struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
struct ufs_qcom_phy *common_cfg,
const struct phy_ops *ufs_qcom_phy_gen_ops,
struct ufs_qcom_phy_specific_ops *phy_spec_ops)
{
int err;
struct device *dev = &pdev->dev;
struct phy *generic_phy = NULL;
struct phy_provider *phy_provider;
err = ufs_qcom_phy_base_init(pdev, common_cfg);
if (err) {
dev_err(dev, "%s: phy base init failed %d\n", __func__, err);
goto out;
}
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
if (IS_ERR(phy_provider)) {
err = PTR_ERR(phy_provider);
dev_err(dev, "%s: failed to register phy %d\n", __func__, err);
goto out;
}
generic_phy = devm_phy_create(dev, NULL, ufs_qcom_phy_gen_ops);
if (IS_ERR(generic_phy)) {
err = PTR_ERR(generic_phy);
dev_err(dev, "%s: failed to create phy %d\n", __func__, err);
generic_phy = NULL;
goto out;
}
common_cfg->phy_spec_ops = phy_spec_ops;
common_cfg->dev = dev;
out:
return generic_phy;
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_generic_probe);
static int ufs_qcom_phy_get_reset(struct ufs_qcom_phy *phy_common)
{
struct reset_control *reset;
if (phy_common->ufs_reset)
return 0;
reset = devm_reset_control_get_exclusive_by_index(phy_common->dev, 0);
if (IS_ERR(reset))
return PTR_ERR(reset);
phy_common->ufs_reset = reset;
return 0;
}
static int __ufs_qcom_phy_clk_get(struct device *dev,
const char *name, struct clk **clk_out, bool err_print)
{
struct clk *clk;
int err = 0;
clk = devm_clk_get(dev, name);
if (IS_ERR(clk)) {
err = PTR_ERR(clk);
if (err_print)
dev_err(dev, "failed to get %s err %d", name, err);
} else {
*clk_out = clk;
}
return err;
}
static int ufs_qcom_phy_clk_get(struct device *dev,
const char *name, struct clk **clk_out)
{
return __ufs_qcom_phy_clk_get(dev, name, clk_out, true);
}
int ufs_qcom_phy_init_clks(struct ufs_qcom_phy *phy_common)
{
int err;
if (of_device_is_compatible(phy_common->dev->of_node,
"qcom,msm8996-ufs-phy-qmp-14nm"))
goto skip_txrx_clk;
err = ufs_qcom_phy_clk_get(phy_common->dev, "tx_iface_clk",
&phy_common->tx_iface_clk);
if (err)
goto out;
err = ufs_qcom_phy_clk_get(phy_common->dev, "rx_iface_clk",
&phy_common->rx_iface_clk);
if (err)
goto out;
skip_txrx_clk:
err = ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk_src",
&phy_common->ref_clk_src);
if (err)
goto out;
/*
* "ref_clk_parent" is optional hence don't abort init if it's not
* found.
*/
__ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk_parent",
&phy_common->ref_clk_parent, false);
err = ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk",
&phy_common->ref_clk);
out:
return err;
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_init_clks);
static int ufs_qcom_phy_init_vreg(struct device *dev,
struct ufs_qcom_phy_vreg *vreg,
const char *name)
{
int err = 0;
char prop_name[MAX_PROP_NAME];
vreg->name = name;
vreg->reg = devm_regulator_get(dev, name);
if (IS_ERR(vreg->reg)) {
err = PTR_ERR(vreg->reg);
dev_err(dev, "failed to get %s, %d\n", name, err);
goto out;
}
if (dev->of_node) {
snprintf(prop_name, MAX_PROP_NAME, "%s-max-microamp", name);
err = of_property_read_u32(dev->of_node,
prop_name, &vreg->max_uA);
if (err && err != -EINVAL) {
dev_err(dev, "%s: failed to read %s\n",
__func__, prop_name);
goto out;
} else if (err == -EINVAL || !vreg->max_uA) {
if (regulator_count_voltages(vreg->reg) > 0) {
dev_err(dev, "%s: %s is mandatory\n",
__func__, prop_name);
goto out;
}
err = 0;
}
}
if (!strcmp(name, "vdda-pll")) {
vreg->max_uV = VDDA_PLL_MAX_UV;
vreg->min_uV = VDDA_PLL_MIN_UV;
} else if (!strcmp(name, "vdda-phy")) {
vreg->max_uV = VDDA_PHY_MAX_UV;
vreg->min_uV = VDDA_PHY_MIN_UV;
} else if (!strcmp(name, "vddp-ref-clk")) {
vreg->max_uV = VDDP_REF_CLK_MAX_UV;
vreg->min_uV = VDDP_REF_CLK_MIN_UV;
}
out:
return err;
}
int ufs_qcom_phy_init_vregulators(struct ufs_qcom_phy *phy_common)
{
int err;
err = ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vdda_pll,
"vdda-pll");
if (err)
goto out;
err = ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vdda_phy,
"vdda-phy");
if (err)
goto out;
err = ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vddp_ref_clk,
"vddp-ref-clk");
out:
return err;
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_init_vregulators);
static int ufs_qcom_phy_cfg_vreg(struct device *dev,
struct ufs_qcom_phy_vreg *vreg, bool on)
{
int ret = 0;
struct regulator *reg = vreg->reg;
const char *name = vreg->name;
int min_uV;
int uA_load;
if (regulator_count_voltages(reg) > 0) {
min_uV = on ? vreg->min_uV : 0;
ret = regulator_set_voltage(reg, min_uV, vreg->max_uV);
if (ret) {
dev_err(dev, "%s: %s set voltage failed, err=%d\n",
__func__, name, ret);
goto out;
}
uA_load = on ? vreg->max_uA : 0;
ret = regulator_set_load(reg, uA_load);
if (ret >= 0) {
/*
* regulator_set_load() returns new regulator
* mode upon success.
*/
ret = 0;
} else {
dev_err(dev, "%s: %s set optimum mode(uA_load=%d) failed, err=%d\n",
__func__, name, uA_load, ret);
goto out;
}
}
out:
return ret;
}
static int ufs_qcom_phy_enable_vreg(struct device *dev,
struct ufs_qcom_phy_vreg *vreg)
{
int ret = 0;
if (!vreg || vreg->enabled)
goto out;
ret = ufs_qcom_phy_cfg_vreg(dev, vreg, true);
if (ret) {
dev_err(dev, "%s: ufs_qcom_phy_cfg_vreg() failed, err=%d\n",
__func__, ret);
goto out;
}
ret = regulator_enable(vreg->reg);
if (ret) {
dev_err(dev, "%s: enable failed, err=%d\n",
__func__, ret);
goto out;
}
vreg->enabled = true;
out:
return ret;
}
static int ufs_qcom_phy_enable_ref_clk(struct ufs_qcom_phy *phy)
{
int ret = 0;
if (phy->is_ref_clk_enabled)
goto out;
/*
* reference clock is propagated in a daisy-chained manner from
* source to phy, so ungate them at each stage.
*/
ret = clk_prepare_enable(phy->ref_clk_src);
if (ret) {
dev_err(phy->dev, "%s: ref_clk_src enable failed %d\n",
__func__, ret);
goto out;
}
/*
* "ref_clk_parent" is optional clock hence make sure that clk reference
* is available before trying to enable the clock.
*/
if (phy->ref_clk_parent) {
ret = clk_prepare_enable(phy->ref_clk_parent);
if (ret) {
dev_err(phy->dev, "%s: ref_clk_parent enable failed %d\n",
__func__, ret);
goto out_disable_src;
}
}
ret = clk_prepare_enable(phy->ref_clk);
if (ret) {
dev_err(phy->dev, "%s: ref_clk enable failed %d\n",
__func__, ret);
goto out_disable_parent;
}
phy->is_ref_clk_enabled = true;
goto out;
out_disable_parent:
if (phy->ref_clk_parent)
clk_disable_unprepare(phy->ref_clk_parent);
out_disable_src:
clk_disable_unprepare(phy->ref_clk_src);
out:
return ret;
}
static int ufs_qcom_phy_disable_vreg(struct device *dev,
struct ufs_qcom_phy_vreg *vreg)
{
int ret = 0;
if (!vreg || !vreg->enabled)
goto out;
ret = regulator_disable(vreg->reg);
if (!ret) {
/* ignore errors on applying disable config */
ufs_qcom_phy_cfg_vreg(dev, vreg, false);
vreg->enabled = false;
} else {
dev_err(dev, "%s: %s disable failed, err=%d\n",
__func__, vreg->name, ret);
}
out:
return ret;
}
static void ufs_qcom_phy_disable_ref_clk(struct ufs_qcom_phy *phy)
{
if (phy->is_ref_clk_enabled) {
clk_disable_unprepare(phy->ref_clk);
/*
* "ref_clk_parent" is optional clock hence make sure that clk
* reference is available before trying to disable the clock.
*/
if (phy->ref_clk_parent)
clk_disable_unprepare(phy->ref_clk_parent);
clk_disable_unprepare(phy->ref_clk_src);
phy->is_ref_clk_enabled = false;
}
}
/* Turn ON M-PHY RMMI interface clocks */
static int ufs_qcom_phy_enable_iface_clk(struct ufs_qcom_phy *phy)
{
int ret = 0;
if (phy->is_iface_clk_enabled)
goto out;
ret = clk_prepare_enable(phy->tx_iface_clk);
if (ret) {
dev_err(phy->dev, "%s: tx_iface_clk enable failed %d\n",
__func__, ret);
goto out;
}
ret = clk_prepare_enable(phy->rx_iface_clk);
if (ret) {
clk_disable_unprepare(phy->tx_iface_clk);
dev_err(phy->dev, "%s: rx_iface_clk enable failed %d. disabling also tx_iface_clk\n",
__func__, ret);
goto out;
}
phy->is_iface_clk_enabled = true;
out:
return ret;
}
/* Turn OFF M-PHY RMMI interface clocks */
static void ufs_qcom_phy_disable_iface_clk(struct ufs_qcom_phy *phy)
{
if (phy->is_iface_clk_enabled) {
clk_disable_unprepare(phy->tx_iface_clk);
clk_disable_unprepare(phy->rx_iface_clk);
phy->is_iface_clk_enabled = false;
}
}
static int ufs_qcom_phy_start_serdes(struct ufs_qcom_phy *ufs_qcom_phy)
{
int ret = 0;
if (!ufs_qcom_phy->phy_spec_ops->start_serdes) {
dev_err(ufs_qcom_phy->dev, "%s: start_serdes() callback is not supported\n",
__func__);
ret = -ENOTSUPP;
} else {
ufs_qcom_phy->phy_spec_ops->start_serdes(ufs_qcom_phy);
}
return ret;
}
int ufs_qcom_phy_set_tx_lane_enable(struct phy *generic_phy, u32 tx_lanes)
{
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
int ret = 0;
if (!ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable) {
dev_err(ufs_qcom_phy->dev, "%s: set_tx_lane_enable() callback is not supported\n",
__func__);
ret = -ENOTSUPP;
} else {
ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable(ufs_qcom_phy,
tx_lanes);
}
return ret;
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_set_tx_lane_enable);
void ufs_qcom_phy_save_controller_version(struct phy *generic_phy,
u8 major, u16 minor, u16 step)
{
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
ufs_qcom_phy->host_ctrl_rev_major = major;
ufs_qcom_phy->host_ctrl_rev_minor = minor;
ufs_qcom_phy->host_ctrl_rev_step = step;
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_save_controller_version);
static int ufs_qcom_phy_is_pcs_ready(struct ufs_qcom_phy *ufs_qcom_phy)
{
if (!ufs_qcom_phy->phy_spec_ops->is_physical_coding_sublayer_ready) {
dev_err(ufs_qcom_phy->dev, "%s: is_physical_coding_sublayer_ready() callback is not supported\n",
__func__);
return -ENOTSUPP;
}
return ufs_qcom_phy->phy_spec_ops->
is_physical_coding_sublayer_ready(ufs_qcom_phy);
}
int ufs_qcom_phy_power_on(struct phy *generic_phy)
{
struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
struct device *dev = phy_common->dev;
bool is_rate_B = false;
int err;
err = ufs_qcom_phy_get_reset(phy_common);
if (err)
return err;
err = reset_control_assert(phy_common->ufs_reset);
if (err)
return err;
if (phy_common->mode == PHY_MODE_UFS_HS_B)
is_rate_B = true;
err = phy_common->phy_spec_ops->calibrate(phy_common, is_rate_B);
if (err)
return err;
err = reset_control_deassert(phy_common->ufs_reset);
if (err) {
dev_err(dev, "Failed to assert UFS PHY reset");
return err;
}
err = ufs_qcom_phy_start_serdes(phy_common);
if (err)
return err;
err = ufs_qcom_phy_is_pcs_ready(phy_common);
if (err)
return err;
err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_phy);
if (err) {
dev_err(dev, "%s enable vdda_phy failed, err=%d\n",
__func__, err);
goto out;
}
phy_common->phy_spec_ops->power_control(phy_common, true);
/* vdda_pll also enables ref clock LDOs so enable it first */
err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_pll);
if (err) {
dev_err(dev, "%s enable vdda_pll failed, err=%d\n",
__func__, err);
goto out_disable_phy;
}
err = ufs_qcom_phy_enable_iface_clk(phy_common);
if (err) {
dev_err(dev, "%s enable phy iface clock failed, err=%d\n",
__func__, err);
goto out_disable_pll;
}
err = ufs_qcom_phy_enable_ref_clk(phy_common);
if (err) {
dev_err(dev, "%s enable phy ref clock failed, err=%d\n",
__func__, err);
goto out_disable_iface_clk;
}
/* enable device PHY ref_clk pad rail */
if (phy_common->vddp_ref_clk.reg) {
err = ufs_qcom_phy_enable_vreg(dev,
&phy_common->vddp_ref_clk);
if (err) {
dev_err(dev, "%s enable vddp_ref_clk failed, err=%d\n",
__func__, err);
goto out_disable_ref_clk;
}
}
goto out;
out_disable_ref_clk:
ufs_qcom_phy_disable_ref_clk(phy_common);
out_disable_iface_clk:
ufs_qcom_phy_disable_iface_clk(phy_common);
out_disable_pll:
ufs_qcom_phy_disable_vreg(dev, &phy_common->vdda_pll);
out_disable_phy:
ufs_qcom_phy_disable_vreg(dev, &phy_common->vdda_phy);
out:
return err;
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_on);
int ufs_qcom_phy_power_off(struct phy *generic_phy)
{
struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
phy_common->phy_spec_ops->power_control(phy_common, false);
if (phy_common->vddp_ref_clk.reg)
ufs_qcom_phy_disable_vreg(phy_common->dev,
&phy_common->vddp_ref_clk);
ufs_qcom_phy_disable_ref_clk(phy_common);
ufs_qcom_phy_disable_iface_clk(phy_common);
ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_pll);
ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_phy);
reset_control_assert(phy_common->ufs_reset);
return 0;
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_off);
MODULE_AUTHOR("Yaniv Gardi <ygardi@codeaurora.org>");
MODULE_AUTHOR("Vivek Gautam <vivek.gautam@codeaurora.org>");
MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY");
MODULE_LICENSE("GPL v2");

View File

@ -111,6 +111,7 @@ struct rcar_gen3_chan {
struct work_struct work;
struct mutex lock; /* protects rphys[...].powered */
enum usb_dr_mode dr_mode;
int irq;
bool extcon_host;
bool is_otg_channel;
bool uses_otg_pins;
@ -389,12 +390,40 @@ static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch)
rcar_gen3_device_recognition(ch);
}
static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch)
{
struct rcar_gen3_chan *ch = _ch;
void __iomem *usb2_base = ch->base;
u32 status = readl(usb2_base + USB2_OBINTSTA);
irqreturn_t ret = IRQ_NONE;
if (status & USB2_OBINT_BITS) {
dev_vdbg(ch->dev, "%s: %08x\n", __func__, status);
writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA);
rcar_gen3_device_recognition(ch);
ret = IRQ_HANDLED;
}
return ret;
}
static int rcar_gen3_phy_usb2_init(struct phy *p)
{
struct rcar_gen3_phy *rphy = phy_get_drvdata(p);
struct rcar_gen3_chan *channel = rphy->ch;
void __iomem *usb2_base = channel->base;
u32 val;
int ret;
if (!rcar_gen3_is_any_rphy_initialized(channel) && channel->irq >= 0) {
INIT_WORK(&channel->work, rcar_gen3_phy_usb2_work);
ret = request_irq(channel->irq, rcar_gen3_phy_usb2_irq,
IRQF_SHARED, dev_name(channel->dev), channel);
if (ret < 0) {
dev_err(channel->dev, "No irq handler (%d)\n", channel->irq);
return ret;
}
}
/* Initialize USB2 part */
val = readl(usb2_base + USB2_INT_ENABLE);
@ -433,6 +462,9 @@ static int rcar_gen3_phy_usb2_exit(struct phy *p)
val &= ~USB2_INT_ENABLE_UCOM_INTEN;
writel(val, usb2_base + USB2_INT_ENABLE);
if (channel->irq >= 0 && !rcar_gen3_is_any_rphy_initialized(channel))
free_irq(channel->irq, channel);
return 0;
}
@ -503,23 +535,6 @@ static const struct phy_ops rz_g1c_phy_usb2_ops = {
.owner = THIS_MODULE,
};
static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch)
{
struct rcar_gen3_chan *ch = _ch;
void __iomem *usb2_base = ch->base;
u32 status = readl(usb2_base + USB2_OBINTSTA);
irqreturn_t ret = IRQ_NONE;
if (status & USB2_OBINT_BITS) {
dev_vdbg(ch->dev, "%s: %08x\n", __func__, status);
writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA);
rcar_gen3_device_recognition(ch);
ret = IRQ_HANDLED;
}
return ret;
}
static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = {
{
.compatible = "renesas,usb2-phy-r8a77470",
@ -598,7 +613,7 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev)
struct phy_provider *provider;
struct resource *res;
const struct phy_ops *phy_usb2_ops;
int irq, ret = 0, i;
int ret = 0, i;
if (!dev->of_node) {
dev_err(dev, "This driver needs device tree\n");
@ -614,16 +629,8 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev)
if (IS_ERR(channel->base))
return PTR_ERR(channel->base);
/* call request_irq for OTG */
irq = platform_get_irq_optional(pdev, 0);
if (irq >= 0) {
INIT_WORK(&channel->work, rcar_gen3_phy_usb2_work);
irq = devm_request_irq(dev, irq, rcar_gen3_phy_usb2_irq,
IRQF_SHARED, dev_name(dev), channel);
if (irq < 0)
dev_err(dev, "No irq handler (%d)\n", irq);
}
/* get irq number here and request_irq for OTG in phy_init */
channel->irq = platform_get_irq_optional(pdev, 0);
channel->dr_mode = rcar_gen3_get_dr_mode(dev->of_node);
if (channel->dr_mode != USB_DR_MODE_UNKNOWN) {
int ret;

View File

@ -347,7 +347,7 @@ struct usb3phy_reg {
};
/**
* struct rockchip_usb3phy_port_cfg: usb3-phy port configuration.
* struct rockchip_usb3phy_port_cfg - usb3-phy port configuration.
* @reg: the base address for usb3-phy config.
* @typec_conn_dir: the register of type-c connector direction.
* @usb3tousb2_en: the register of type-c force usb2 to usb2 enable.

View File

@ -3,23 +3,23 @@
# Phy drivers for Samsung platforms
#
config PHY_EXYNOS_DP_VIDEO
tristate "EXYNOS SoC series Display Port PHY driver"
tristate "Exynos SoC series Display Port PHY driver"
depends on OF
depends on ARCH_EXYNOS || COMPILE_TEST
default ARCH_EXYNOS
select GENERIC_PHY
help
Support for Display Port PHY found on Samsung EXYNOS SoCs.
Support for Display Port PHY found on Samsung Exynos SoCs.
config PHY_EXYNOS_MIPI_VIDEO
tristate "S5P/EXYNOS SoC series MIPI CSI-2/DSI PHY driver"
tristate "S5P/Exynos SoC series MIPI CSI-2/DSI PHY driver"
depends on HAS_IOMEM
depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST
select GENERIC_PHY
default y if ARCH_S5PV210 || ARCH_EXYNOS
help
Support for MIPI CSI-2 and MIPI DSI DPHY found on Samsung S5P
and EXYNOS SoCs.
and Exynos SoCs.
config PHY_EXYNOS_PCIE
bool "Exynos PCIe PHY driver"
@ -29,6 +29,15 @@ config PHY_EXYNOS_PCIE
Enable PCIe PHY support for Exynos SoC series.
This driver provides PHY interface for Exynos PCIe controller.
config PHY_SAMSUNG_UFS
tristate "SAMSUNG SoC series UFS PHY driver"
depends on OF && (ARCH_EXYNOS || COMPILE_TEST)
select GENERIC_PHY
help
Enable this to support the Samsung UFS PHY driver for
Samsung SoCs. This driver provides the interface for UFS
host controller to do PHY related programming.
config PHY_SAMSUNG_USB2
tristate "Samsung USB 2.0 PHY driver"
depends on HAS_IOMEM

View File

@ -2,6 +2,7 @@
obj-$(CONFIG_PHY_EXYNOS_DP_VIDEO) += phy-exynos-dp-video.o
obj-$(CONFIG_PHY_EXYNOS_MIPI_VIDEO) += phy-exynos-mipi-video.o
obj-$(CONFIG_PHY_EXYNOS_PCIE) += phy-exynos-pcie.o
obj-$(CONFIG_PHY_SAMSUNG_UFS) += phy-samsung-ufs.o
obj-$(CONFIG_PHY_SAMSUNG_USB2) += phy-exynos-usb2.o
phy-exynos-usb2-y += phy-samsung-usb2.o
phy-exynos-usb2-$(CONFIG_PHY_EXYNOS4210_USB2) += phy-exynos4210-usb2.o

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Samsung EXYNOS SoC series Display Port PHY driver
* Samsung Exynos SoC series Display Port PHY driver
*
* Copyright (C) 2013 Samsung Electronics Co., Ltd.
* Author: Jingoo Han <jg1.han@samsung.com>
@ -115,5 +115,5 @@ static struct platform_driver exynos_dp_video_phy_driver = {
module_platform_driver(exynos_dp_video_phy_driver);
MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
MODULE_DESCRIPTION("Samsung EXYNOS SoC DP PHY driver");
MODULE_DESCRIPTION("Samsung Exynos SoC DP PHY driver");
MODULE_LICENSE("GPL v2");

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY driver
* Samsung S5P/Exynos SoC series MIPI CSIS/DSIM DPHY driver
*
* Copyright (C) 2013,2016 Samsung Electronics Co., Ltd.
* Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
@ -364,6 +364,6 @@ static struct platform_driver exynos_mipi_video_phy_driver = {
};
module_platform_driver(exynos_mipi_video_phy_driver);
MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC MIPI CSI-2/DSI PHY driver");
MODULE_DESCRIPTION("Samsung S5P/Exynos SoC MIPI CSI-2/DSI PHY driver");
MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>");
MODULE_LICENSE("GPL v2");

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Samsung EXYNOS SoC series PCIe PHY driver
* Samsung Exynos SoC series PCIe PHY driver
*
* Phy provider for PCIe controller on Exynos SoC series
*

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Samsung EXYNOS5 SoC series USB DRD PHY driver
* Samsung Exynos5 SoC series USB DRD PHY driver
*
* Phy provider for USB 3.0 DRD controller on Exynos5 SoC series
*
@ -33,7 +33,7 @@
#define EXYNOS5_FSEL_24MHZ 0x5
#define EXYNOS5_FSEL_50MHZ 0x7
/* EXYNOS5: USB 3.0 DRD PHY registers */
/* Exynos5: USB 3.0 DRD PHY registers */
#define EXYNOS5_DRD_LINKSYSTEM 0x04
#define LINKSYSTEM_FLADJ_MASK (0x3f << 1)
@ -180,14 +180,14 @@ struct exynos5_usbdrd_phy_drvdata {
* @utmiclk: clock for utmi+ phy
* @itpclk: clock for ITP generation
* @drv_data: pointer to SoC level driver data structure
* @phys[]: array for 'EXYNOS5_DRDPHYS_NUM' number of PHY
* @phys: array for 'EXYNOS5_DRDPHYS_NUM' number of PHY
* instances each with its 'phy' and 'phy_cfg'.
* @extrefclk: frequency select settings when using 'separate
* reference clocks' for SS and HS operations
* @ref_clk: reference clock to PHY block from which PHY's
* operational clocks are derived
* vbus: VBUS regulator for phy
* vbus_boost: Boost regulator for VBUS present on few Exynos boards
* @vbus: VBUS regulator for phy
* @vbus_boost: Boost regulator for VBUS present on few Exynos boards
*/
struct exynos5_usbdrd_phy {
struct device *dev;
@ -714,7 +714,9 @@ static int exynos5_usbdrd_phy_calibrate(struct phy *phy)
struct phy_usb_instance *inst = phy_get_drvdata(phy);
struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst);
return exynos5420_usbdrd_phy_calibrate(phy_drd);
if (inst->phy_cfg->id == EXYNOS5_DRDPHY_UTMI)
return exynos5420_usbdrd_phy_calibrate(phy_drd);
return 0;
}
static const struct phy_ops exynos5_usbdrd_phy_ops = {
@ -958,7 +960,7 @@ static struct platform_driver exynos5_usb3drd_phy = {
};
module_platform_driver(exynos5_usb3drd_phy);
MODULE_DESCRIPTION("Samsung EXYNOS5 SoCs USB 3.0 DRD controller PHY driver");
MODULE_DESCRIPTION("Samsung Exynos5 SoCs USB 3.0 DRD controller PHY driver");
MODULE_AUTHOR("Vivek Gautam <gautam.vivek@samsung.com>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:exynos5_usb3drd_phy");

View File

@ -0,0 +1,81 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* UFS PHY driver data for Samsung EXYNOS7 SoC
*
* Copyright (C) 2020 Samsung Electronics Co., Ltd.
*/
#ifndef _PHY_EXYNOS7_UFS_H_
#define _PHY_EXYNOS7_UFS_H_
#include "phy-samsung-ufs.h"
#define EXYNOS7_EMBEDDED_COMBO_PHY_CTRL 0x720
#define EXYNOS7_EMBEDDED_COMBO_PHY_CTRL_MASK 0x1
#define EXYNOS7_EMBEDDED_COMBO_PHY_CTRL_EN BIT(0)
/* Calibration for phy initialization */
static const struct samsung_ufs_phy_cfg exynos7_pre_init_cfg[] = {
PHY_COMN_REG_CFG(0x00f, 0xfa, PWR_MODE_ANY),
PHY_COMN_REG_CFG(0x010, 0x82, PWR_MODE_ANY),
PHY_COMN_REG_CFG(0x011, 0x1e, PWR_MODE_ANY),
PHY_COMN_REG_CFG(0x017, 0x84, PWR_MODE_ANY),
PHY_TRSV_REG_CFG(0x035, 0x58, PWR_MODE_ANY),
PHY_TRSV_REG_CFG(0x036, 0x32, PWR_MODE_ANY),
PHY_TRSV_REG_CFG(0x037, 0x40, PWR_MODE_ANY),
PHY_TRSV_REG_CFG(0x03b, 0x83, PWR_MODE_ANY),
PHY_TRSV_REG_CFG(0x042, 0x88, PWR_MODE_ANY),
PHY_TRSV_REG_CFG(0x043, 0xa6, PWR_MODE_ANY),
PHY_TRSV_REG_CFG(0x048, 0x74, PWR_MODE_ANY),
PHY_TRSV_REG_CFG(0x04c, 0x5b, PWR_MODE_ANY),
PHY_TRSV_REG_CFG(0x04d, 0x83, PWR_MODE_ANY),
PHY_TRSV_REG_CFG(0x05c, 0x14, PWR_MODE_ANY),
END_UFS_PHY_CFG
};
/* Calibration for HS mode series A/B */
static const struct samsung_ufs_phy_cfg exynos7_pre_pwr_hs_cfg[] = {
PHY_COMN_REG_CFG(0x00f, 0xfa, PWR_MODE_HS_ANY),
PHY_COMN_REG_CFG(0x010, 0x82, PWR_MODE_HS_ANY),
PHY_COMN_REG_CFG(0x011, 0x1e, PWR_MODE_HS_ANY),
/* Setting order: 1st(0x16, 2nd(0x15) */
PHY_COMN_REG_CFG(0x016, 0xff, PWR_MODE_HS_ANY),
PHY_COMN_REG_CFG(0x015, 0x80, PWR_MODE_HS_ANY),
PHY_COMN_REG_CFG(0x017, 0x94, PWR_MODE_HS_ANY),
PHY_TRSV_REG_CFG(0x036, 0x32, PWR_MODE_HS_ANY),
PHY_TRSV_REG_CFG(0x037, 0x43, PWR_MODE_HS_ANY),
PHY_TRSV_REG_CFG(0x038, 0x3f, PWR_MODE_HS_ANY),
PHY_TRSV_REG_CFG(0x042, 0x88, PWR_MODE_HS_G2_SER_A),
PHY_TRSV_REG_CFG(0x042, 0xbb, PWR_MODE_HS_G2_SER_B),
PHY_TRSV_REG_CFG(0x043, 0xa6, PWR_MODE_HS_ANY),
PHY_TRSV_REG_CFG(0x048, 0x74, PWR_MODE_HS_ANY),
PHY_TRSV_REG_CFG(0x034, 0x35, PWR_MODE_HS_G2_SER_A),
PHY_TRSV_REG_CFG(0x034, 0x36, PWR_MODE_HS_G2_SER_B),
PHY_TRSV_REG_CFG(0x035, 0x5b, PWR_MODE_HS_G2_SER_A),
PHY_TRSV_REG_CFG(0x035, 0x5c, PWR_MODE_HS_G2_SER_B),
END_UFS_PHY_CFG
};
/* Calibration for HS mode series A/B atfer PMC */
static const struct samsung_ufs_phy_cfg exynos7_post_pwr_hs_cfg[] = {
PHY_COMN_REG_CFG(0x015, 0x00, PWR_MODE_HS_ANY),
PHY_TRSV_REG_CFG(0x04d, 0x83, PWR_MODE_HS_ANY),
END_UFS_PHY_CFG
};
static const struct samsung_ufs_phy_cfg *exynos7_ufs_phy_cfgs[CFG_TAG_MAX] = {
[CFG_PRE_INIT] = exynos7_pre_init_cfg,
[CFG_PRE_PWR_HS] = exynos7_pre_pwr_hs_cfg,
[CFG_POST_PWR_HS] = exynos7_post_pwr_hs_cfg,
};
static struct samsung_ufs_phy_drvdata exynos7_ufs_phy = {
.cfg = exynos7_ufs_phy_cfgs,
.isol = {
.offset = EXYNOS7_EMBEDDED_COMBO_PHY_CTRL,
.mask = EXYNOS7_EMBEDDED_COMBO_PHY_CTRL_MASK,
.en = EXYNOS7_EMBEDDED_COMBO_PHY_CTRL_EN,
},
.has_symbol_clk = 1,
};
#endif /* _PHY_EXYNOS7_UFS_H_ */

View File

@ -0,0 +1,366 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* UFS PHY driver for Samsung SoC
*
* Copyright (C) 2020 Samsung Electronics Co., Ltd.
* Author: Seungwon Jeon <essuuj@gmail.com>
* Author: Alim Akhtar <alim.akhtar@samsung.com>
*
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include "phy-samsung-ufs.h"
#define for_each_phy_lane(phy, i) \
for (i = 0; i < (phy)->lane_cnt; i++)
#define for_each_phy_cfg(cfg) \
for (; (cfg)->id; (cfg)++)
#define PHY_DEF_LANE_CNT 1
static void samsung_ufs_phy_config(struct samsung_ufs_phy *phy,
const struct samsung_ufs_phy_cfg *cfg,
u8 lane)
{
enum {LANE_0, LANE_1}; /* lane index */
switch (lane) {
case LANE_0:
writel(cfg->val, (phy)->reg_pma + cfg->off_0);
break;
case LANE_1:
if (cfg->id == PHY_TRSV_BLK)
writel(cfg->val, (phy)->reg_pma + cfg->off_1);
break;
}
}
static int samsung_ufs_phy_wait_for_lock_acq(struct phy *phy)
{
struct samsung_ufs_phy *ufs_phy = get_samsung_ufs_phy(phy);
const unsigned int timeout_us = 100000;
const unsigned int sleep_us = 10;
u32 val;
int err;
err = readl_poll_timeout(
ufs_phy->reg_pma + PHY_APB_ADDR(PHY_PLL_LOCK_STATUS),
val, (val & PHY_PLL_LOCK_BIT), sleep_us, timeout_us);
if (err) {
dev_err(ufs_phy->dev,
"failed to get phy pll lock acquisition %d\n", err);
goto out;
}
err = readl_poll_timeout(
ufs_phy->reg_pma + PHY_APB_ADDR(PHY_CDR_LOCK_STATUS),
val, (val & PHY_CDR_LOCK_BIT), sleep_us, timeout_us);
if (err)
dev_err(ufs_phy->dev,
"failed to get phy cdr lock acquisition %d\n", err);
out:
return err;
}
static int samsung_ufs_phy_calibrate(struct phy *phy)
{
struct samsung_ufs_phy *ufs_phy = get_samsung_ufs_phy(phy);
struct samsung_ufs_phy_cfg **cfgs = ufs_phy->cfg;
const struct samsung_ufs_phy_cfg *cfg;
int err = 0;
int i;
if (unlikely(ufs_phy->ufs_phy_state < CFG_PRE_INIT ||
ufs_phy->ufs_phy_state >= CFG_TAG_MAX)) {
dev_err(ufs_phy->dev, "invalid phy config index %d\n", ufs_phy->ufs_phy_state);
return -EINVAL;
}
cfg = cfgs[ufs_phy->ufs_phy_state];
if (!cfg)
goto out;
for_each_phy_cfg(cfg) {
for_each_phy_lane(ufs_phy, i) {
samsung_ufs_phy_config(ufs_phy, cfg, i);
}
}
if (ufs_phy->ufs_phy_state == CFG_POST_PWR_HS)
err = samsung_ufs_phy_wait_for_lock_acq(phy);
/**
* In Samsung ufshci, PHY need to be calibrated at different
* stages / state mainly before Linkstartup, after Linkstartup,
* before power mode change and after power mode change.
* Below state machine to make sure to calibrate PHY in each
* state. Here after configuring PHY in a given state, will
* change the state to next state so that next state phy
* calibration value can be programed
*/
out:
switch (ufs_phy->ufs_phy_state) {
case CFG_PRE_INIT:
ufs_phy->ufs_phy_state = CFG_POST_INIT;
break;
case CFG_POST_INIT:
ufs_phy->ufs_phy_state = CFG_PRE_PWR_HS;
break;
case CFG_PRE_PWR_HS:
ufs_phy->ufs_phy_state = CFG_POST_PWR_HS;
break;
case CFG_POST_PWR_HS:
/* Change back to INIT state */
ufs_phy->ufs_phy_state = CFG_PRE_INIT;
break;
default:
dev_err(ufs_phy->dev, "wrong state for phy calibration\n");
}
return err;
}
static int samsung_ufs_phy_symbol_clk_init(struct samsung_ufs_phy *phy)
{
int ret;
phy->tx0_symbol_clk = devm_clk_get(phy->dev, "tx0_symbol_clk");
if (IS_ERR(phy->tx0_symbol_clk)) {
dev_err(phy->dev, "failed to get tx0_symbol_clk clock\n");
return PTR_ERR(phy->tx0_symbol_clk);
}
phy->rx0_symbol_clk = devm_clk_get(phy->dev, "rx0_symbol_clk");
if (IS_ERR(phy->rx0_symbol_clk)) {
dev_err(phy->dev, "failed to get rx0_symbol_clk clock\n");
return PTR_ERR(phy->rx0_symbol_clk);
}
phy->rx1_symbol_clk = devm_clk_get(phy->dev, "rx1_symbol_clk");
if (IS_ERR(phy->rx1_symbol_clk)) {
dev_err(phy->dev, "failed to get rx1_symbol_clk clock\n");
return PTR_ERR(phy->rx1_symbol_clk);
}
ret = clk_prepare_enable(phy->tx0_symbol_clk);
if (ret) {
dev_err(phy->dev, "%s: tx0_symbol_clk enable failed %d\n", __func__, ret);
goto out;
}
ret = clk_prepare_enable(phy->rx0_symbol_clk);
if (ret) {
dev_err(phy->dev, "%s: rx0_symbol_clk enable failed %d\n", __func__, ret);
goto out_disable_tx0_clk;
}
ret = clk_prepare_enable(phy->rx1_symbol_clk);
if (ret) {
dev_err(phy->dev, "%s: rx1_symbol_clk enable failed %d\n", __func__, ret);
goto out_disable_rx0_clk;
}
return 0;
out_disable_rx0_clk:
clk_disable_unprepare(phy->rx0_symbol_clk);
out_disable_tx0_clk:
clk_disable_unprepare(phy->tx0_symbol_clk);
out:
return ret;
}
static int samsung_ufs_phy_clks_init(struct samsung_ufs_phy *phy)
{
int ret;
phy->ref_clk = devm_clk_get(phy->dev, "ref_clk");
if (IS_ERR(phy->ref_clk))
dev_err(phy->dev, "failed to get ref_clk clock\n");
ret = clk_prepare_enable(phy->ref_clk);
if (ret) {
dev_err(phy->dev, "%s: ref_clk enable failed %d\n", __func__, ret);
return ret;
}
dev_dbg(phy->dev, "UFS MPHY ref_clk_rate = %ld\n", clk_get_rate(phy->ref_clk));
return 0;
}
static int samsung_ufs_phy_init(struct phy *phy)
{
struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy);
int ret;
ss_phy->lane_cnt = phy->attrs.bus_width;
ss_phy->ufs_phy_state = CFG_PRE_INIT;
if (ss_phy->drvdata->has_symbol_clk) {
ret = samsung_ufs_phy_symbol_clk_init(ss_phy);
if (ret)
dev_err(ss_phy->dev, "failed to set ufs phy symbol clocks\n");
}
ret = samsung_ufs_phy_clks_init(ss_phy);
if (ret)
dev_err(ss_phy->dev, "failed to set ufs phy clocks\n");
ret = samsung_ufs_phy_calibrate(phy);
if (ret)
dev_err(ss_phy->dev, "ufs phy calibration failed\n");
return ret;
}
static int samsung_ufs_phy_power_on(struct phy *phy)
{
struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy);
samsung_ufs_phy_ctrl_isol(ss_phy, false);
return 0;
}
static int samsung_ufs_phy_power_off(struct phy *phy)
{
struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy);
samsung_ufs_phy_ctrl_isol(ss_phy, true);
return 0;
}
static int samsung_ufs_phy_set_mode(struct phy *generic_phy,
enum phy_mode mode, int submode)
{
struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(generic_phy);
ss_phy->mode = PHY_MODE_INVALID;
if (mode > 0)
ss_phy->mode = mode;
return 0;
}
static int samsung_ufs_phy_exit(struct phy *phy)
{
struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy);
clk_disable_unprepare(ss_phy->ref_clk);
if (ss_phy->drvdata->has_symbol_clk) {
clk_disable_unprepare(ss_phy->tx0_symbol_clk);
clk_disable_unprepare(ss_phy->rx0_symbol_clk);
clk_disable_unprepare(ss_phy->rx1_symbol_clk);
}
return 0;
}
static struct phy_ops samsung_ufs_phy_ops = {
.init = samsung_ufs_phy_init,
.exit = samsung_ufs_phy_exit,
.power_on = samsung_ufs_phy_power_on,
.power_off = samsung_ufs_phy_power_off,
.calibrate = samsung_ufs_phy_calibrate,
.set_mode = samsung_ufs_phy_set_mode,
.owner = THIS_MODULE,
};
static const struct of_device_id samsung_ufs_phy_match[];
static int samsung_ufs_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct of_device_id *match;
struct samsung_ufs_phy *phy;
struct phy *gen_phy;
struct phy_provider *phy_provider;
const struct samsung_ufs_phy_drvdata *drvdata;
int err = 0;
match = of_match_node(samsung_ufs_phy_match, dev->of_node);
if (!match) {
err = -EINVAL;
dev_err(dev, "failed to get match_node\n");
goto out;
}
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy) {
err = -ENOMEM;
goto out;
}
phy->reg_pma = devm_platform_ioremap_resource_byname(pdev, "phy-pma");
if (IS_ERR(phy->reg_pma)) {
err = PTR_ERR(phy->reg_pma);
goto out;
}
phy->reg_pmu = syscon_regmap_lookup_by_phandle(
dev->of_node, "samsung,pmu-syscon");
if (IS_ERR(phy->reg_pmu)) {
err = PTR_ERR(phy->reg_pmu);
dev_err(dev, "failed syscon remap for pmu\n");
goto out;
}
gen_phy = devm_phy_create(dev, NULL, &samsung_ufs_phy_ops);
if (IS_ERR(gen_phy)) {
err = PTR_ERR(gen_phy);
dev_err(dev, "failed to create PHY for ufs-phy\n");
goto out;
}
drvdata = match->data;
phy->dev = dev;
phy->drvdata = drvdata;
phy->cfg = (struct samsung_ufs_phy_cfg **)drvdata->cfg;
phy->isol = &drvdata->isol;
phy->lane_cnt = PHY_DEF_LANE_CNT;
phy_set_drvdata(gen_phy, phy);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
if (IS_ERR(phy_provider)) {
err = PTR_ERR(phy_provider);
dev_err(dev, "failed to register phy-provider\n");
goto out;
}
out:
return err;
}
static const struct of_device_id samsung_ufs_phy_match[] = {
{
.compatible = "samsung,exynos7-ufs-phy",
.data = &exynos7_ufs_phy,
},
{},
};
MODULE_DEVICE_TABLE(of, samsung_ufs_phy_match);
static struct platform_driver samsung_ufs_phy_driver = {
.probe = samsung_ufs_phy_probe,
.driver = {
.name = "samsung-ufs-phy",
.of_match_table = samsung_ufs_phy_match,
},
};
module_platform_driver(samsung_ufs_phy_driver);
MODULE_DESCRIPTION("Samsung SoC UFS PHY Driver");
MODULE_AUTHOR("Seungwon Jeon <essuuj@gmail.com>");
MODULE_AUTHOR("Alim Akhtar <alim.akhtar@samsung.com>");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,139 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* UFS PHY driver for Samsung EXYNOS SoC
*
* Copyright (C) 2020 Samsung Electronics Co., Ltd.
* Author: Seungwon Jeon <essuuj@gmail.com>
* Author: Alim Akhtar <alim.akhtar@samsung.com>
*
*/
#ifndef _PHY_SAMSUNG_UFS_
#define _PHY_SAMSUNG_UFS_
#define PHY_COMN_BLK 1
#define PHY_TRSV_BLK 2
#define END_UFS_PHY_CFG { 0 }
#define PHY_TRSV_CH_OFFSET 0x30
#define PHY_APB_ADDR(off) ((off) << 2)
#define PHY_COMN_REG_CFG(o, v, d) { \
.off_0 = PHY_APB_ADDR((o)), \
.off_1 = 0, \
.val = (v), \
.desc = (d), \
.id = PHY_COMN_BLK, \
}
#define PHY_TRSV_REG_CFG(o, v, d) { \
.off_0 = PHY_APB_ADDR((o)), \
.off_1 = PHY_APB_ADDR((o) + PHY_TRSV_CH_OFFSET), \
.val = (v), \
.desc = (d), \
.id = PHY_TRSV_BLK, \
}
/* UFS PHY registers */
#define PHY_PLL_LOCK_STATUS 0x1e
#define PHY_CDR_LOCK_STATUS 0x5e
#define PHY_PLL_LOCK_BIT BIT(5)
#define PHY_CDR_LOCK_BIT BIT(4)
/* description for PHY calibration */
enum {
/* applicable to any */
PWR_DESC_ANY = 0,
/* mode */
PWR_DESC_PWM = 1,
PWR_DESC_HS = 2,
/* series */
PWR_DESC_SER_A = 1,
PWR_DESC_SER_B = 2,
/* gear */
PWR_DESC_G1 = 1,
PWR_DESC_G2 = 2,
PWR_DESC_G3 = 3,
/* field mask */
MD_MASK = 0x3,
SR_MASK = 0x3,
GR_MASK = 0x7,
};
#define PWR_MODE_HS_G1_ANY PWR_MODE_HS(PWR_DESC_G1, PWR_DESC_ANY)
#define PWR_MODE_HS_G1_SER_A PWR_MODE_HS(PWR_DESC_G1, PWR_DESC_SER_A)
#define PWR_MODE_HS_G1_SER_B PWR_MODE_HS(PWR_DESC_G1, PWR_DESC_SER_B)
#define PWR_MODE_HS_G2_ANY PWR_MODE_HS(PWR_DESC_G2, PWR_DESC_ANY)
#define PWR_MODE_HS_G2_SER_A PWR_MODE_HS(PWR_DESC_G2, PWR_DESC_SER_A)
#define PWR_MODE_HS_G2_SER_B PWR_MODE_HS(PWR_DESC_G2, PWR_DESC_SER_B)
#define PWR_MODE_HS_G3_ANY PWR_MODE_HS(PWR_DESC_G3, PWR_DESC_ANY)
#define PWR_MODE_HS_G3_SER_A PWR_MODE_HS(PWR_DESC_G3, PWR_DESC_SER_A)
#define PWR_MODE_HS_G3_SER_B PWR_MODE_HS(PWR_DESC_G3, PWR_DESC_SER_B)
#define PWR_MODE(g, s, m) ((((g) & GR_MASK) << 4) |\
(((s) & SR_MASK) << 2) | ((m) & MD_MASK))
#define PWR_MODE_PWM_ANY PWR_MODE(PWR_DESC_ANY,\
PWR_DESC_ANY, PWR_DESC_PWM)
#define PWR_MODE_HS(g, s) ((((g) & GR_MASK) << 4) |\
(((s) & SR_MASK) << 2) | PWR_DESC_HS)
#define PWR_MODE_HS_ANY PWR_MODE(PWR_DESC_ANY,\
PWR_DESC_ANY, PWR_DESC_HS)
#define PWR_MODE_ANY PWR_MODE(PWR_DESC_ANY,\
PWR_DESC_ANY, PWR_DESC_ANY)
/* PHY calibration point/state */
enum {
CFG_PRE_INIT,
CFG_POST_INIT,
CFG_PRE_PWR_HS,
CFG_POST_PWR_HS,
CFG_TAG_MAX,
};
struct samsung_ufs_phy_cfg {
u32 off_0;
u32 off_1;
u32 val;
u8 desc;
u8 id;
};
struct samsung_ufs_phy_drvdata {
const struct samsung_ufs_phy_cfg **cfg;
struct pmu_isol {
u32 offset;
u32 mask;
u32 en;
} isol;
bool has_symbol_clk;
};
struct samsung_ufs_phy {
struct device *dev;
void __iomem *reg_pma;
struct regmap *reg_pmu;
struct clk *ref_clk;
struct clk *ref_clk_parent;
struct clk *tx0_symbol_clk;
struct clk *rx0_symbol_clk;
struct clk *rx1_symbol_clk;
const struct samsung_ufs_phy_drvdata *drvdata;
struct samsung_ufs_phy_cfg **cfg;
const struct pmu_isol *isol;
u8 lane_cnt;
int ufs_phy_state;
enum phy_mode mode;
};
static inline struct samsung_ufs_phy *get_samsung_ufs_phy(struct phy *phy)
{
return (struct samsung_ufs_phy *)phy_get_drvdata(phy);
}
static inline void samsung_ufs_phy_ctrl_isol(
struct samsung_ufs_phy *phy, u32 isol)
{
regmap_update_bits(phy->reg_pmu, phy->isol->offset,
phy->isol->mask, isol ? 0 : phy->isol->en);
}
#include "phy-exynos7-ufs.h"
#endif /* _PHY_SAMSUNG_UFS_ */

View File

@ -255,7 +255,7 @@ static struct platform_driver samsung_usb2_phy_driver = {
};
module_platform_driver(samsung_usb2_phy_driver);
MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC USB PHY driver");
MODULE_DESCRIPTION("Samsung S5P/Exynos SoC USB PHY driver");
MODULE_AUTHOR("Kamil Debski <k.debski@samsung.com>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:samsung-usb2-phy");

View File

@ -327,7 +327,7 @@ static int stm32_usbphyc_probe(struct platform_device *pdev)
if (IS_ERR(usbphyc->base))
return PTR_ERR(usbphyc->base);
usbphyc->clk = devm_clk_get(dev, 0);
usbphyc->clk = devm_clk_get(dev, NULL);
if (IS_ERR(usbphyc->clk)) {
ret = PTR_ERR(usbphyc->clk);
dev_err(dev, "clk get failed: %d\n", ret);
@ -340,7 +340,7 @@ static int stm32_usbphyc_probe(struct platform_device *pdev)
return ret;
}
usbphyc->rst = devm_reset_control_get(dev, 0);
usbphyc->rst = devm_reset_control_get(dev, NULL);
if (!IS_ERR(usbphyc->rst)) {
reset_control_assert(usbphyc->rst);
udelay(2);

View File

@ -82,17 +82,16 @@ static int dm816x_usb_phy_init(struct phy *x)
{
struct dm816x_usb_phy *phy = phy_get_drvdata(x);
unsigned int val;
int error;
if (clk_get_rate(phy->refclk) != 24000000)
dev_warn(phy->dev, "nonstandard phy refclk\n");
/* Set PLL ref clock and put phys to sleep */
error = regmap_update_bits(phy->syscon, phy->usb_ctrl,
DM816X_USB_CTRL_PHYCLKSRC |
DM816X_USB_CTRL_PHYSLEEP1 |
DM816X_USB_CTRL_PHYSLEEP0,
0);
regmap_update_bits(phy->syscon, phy->usb_ctrl,
DM816X_USB_CTRL_PHYCLKSRC |
DM816X_USB_CTRL_PHYSLEEP1 |
DM816X_USB_CTRL_PHYSLEEP0,
0);
regmap_read(phy->syscon, phy->usb_ctrl, &val);
if ((val & 3) != 0)
dev_info(phy->dev,

View File

@ -337,7 +337,6 @@ static int ti_pipe3_power_on(struct phy *x)
{
u32 val;
u32 mask;
int ret;
unsigned long rate;
struct ti_pipe3 *phy = phy_get_drvdata(x);
bool rx_pending = false;
@ -355,8 +354,8 @@ static int ti_pipe3_power_on(struct phy *x)
rate = rate / 1000000;
mask = OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK;
val = rate << OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT;
ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg,
mask, val);
regmap_update_bits(phy->phy_power_syscon, phy->power_reg,
mask, val);
/*
* For PCIe, TX and RX must be powered on simultaneously.
* For USB and SATA, TX must be powered on before RX

View File

@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# PHY drivers for Xilinx platforms
#
config PHY_XILINX_ZYNQMP
tristate "Xilinx ZynqMP PHY driver"
depends on ARCH_ZYNQMP || COMPILE_TEST
select GENERIC_PHY
help
Enable this to support ZynqMP High Speed Gigabit Transceiver
that is part of ZynqMP SoC.

View File

@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_PHY_XILINX_ZYNQMP) += phy-zynqmp.o

View File

@ -0,0 +1,993 @@
// SPDX-License-Identifier: GPL-2.0
/*
* phy-zynqmp.c - PHY driver for Xilinx ZynqMP GT.
*
* Copyright (C) 2018-2020 Xilinx Inc.
*
* Author: Anurag Kumar Vulisha <anuragku@xilinx.com>
* Author: Subbaraya Sundeep <sundeep.lkml@gmail.com>
* Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
*
* This driver is tested for USB, SATA and Display Port currently.
* Other controllers PCIe and SGMII should also work but that is
* experimental as of now.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <dt-bindings/phy/phy.h>
/*
* Lane Registers
*/
/* TX De-emphasis parameters */
#define L0_TX_ANA_TM_18 0x0048
#define L0_TX_ANA_TM_118 0x01d8
#define L0_TX_ANA_TM_118_FORCE_17_0 BIT(0)
/* DN Resistor calibration code parameters */
#define L0_TXPMA_ST_3 0x0b0c
#define L0_DN_CALIB_CODE 0x3f
/* PMA control parameters */
#define L0_TXPMD_TM_45 0x0cb4
#define L0_TXPMD_TM_48 0x0cc0
#define L0_TXPMD_TM_45_OVER_DP_MAIN BIT(0)
#define L0_TXPMD_TM_45_ENABLE_DP_MAIN BIT(1)
#define L0_TXPMD_TM_45_OVER_DP_POST1 BIT(2)
#define L0_TXPMD_TM_45_ENABLE_DP_POST1 BIT(3)
#define L0_TXPMD_TM_45_OVER_DP_POST2 BIT(4)
#define L0_TXPMD_TM_45_ENABLE_DP_POST2 BIT(5)
/* PCS control parameters */
#define L0_TM_DIG_6 0x106c
#define L0_TM_DIS_DESCRAMBLE_DECODER 0x0f
#define L0_TX_DIG_61 0x00f4
#define L0_TM_DISABLE_SCRAMBLE_ENCODER 0x0f
/* PLL Test Mode register parameters */
#define L0_TM_PLL_DIG_37 0x2094
#define L0_TM_COARSE_CODE_LIMIT 0x10
/* PLL SSC step size offsets */
#define L0_PLL_SS_STEPS_0_LSB 0x2368
#define L0_PLL_SS_STEPS_1_MSB 0x236c
#define L0_PLL_SS_STEP_SIZE_0_LSB 0x2370
#define L0_PLL_SS_STEP_SIZE_1 0x2374
#define L0_PLL_SS_STEP_SIZE_2 0x2378
#define L0_PLL_SS_STEP_SIZE_3_MSB 0x237c
#define L0_PLL_STATUS_READ_1 0x23e4
/* SSC step size parameters */
#define STEP_SIZE_0_MASK 0xff
#define STEP_SIZE_1_MASK 0xff
#define STEP_SIZE_2_MASK 0xff
#define STEP_SIZE_3_MASK 0x3
#define STEP_SIZE_SHIFT 8
#define FORCE_STEP_SIZE 0x10
#define FORCE_STEPS 0x20
#define STEPS_0_MASK 0xff
#define STEPS_1_MASK 0x07
/* Reference clock selection parameters */
#define L0_Ln_REF_CLK_SEL(n) (0x2860 + (n) * 4)
#define L0_REF_CLK_SEL_MASK 0x8f
/* Calibration digital logic parameters */
#define L3_TM_CALIB_DIG19 0xec4c
#define L3_CALIB_DONE_STATUS 0xef14
#define L3_TM_CALIB_DIG18 0xec48
#define L3_TM_CALIB_DIG19_NSW 0x07
#define L3_TM_CALIB_DIG18_NSW 0xe0
#define L3_TM_OVERRIDE_NSW_CODE 0x20
#define L3_CALIB_DONE 0x02
#define L3_NSW_SHIFT 5
#define L3_NSW_PIPE_SHIFT 4
#define L3_NSW_CALIB_SHIFT 3
#define PHY_REG_OFFSET 0x4000
/*
* Global Registers
*/
/* Refclk selection parameters */
#define PLL_REF_SEL(n) (0x10000 + (n) * 4)
#define PLL_FREQ_MASK 0x1f
#define PLL_STATUS_LOCKED 0x10
/* Inter Connect Matrix parameters */
#define ICM_CFG0 0x10010
#define ICM_CFG1 0x10014
#define ICM_CFG0_L0_MASK 0x07
#define ICM_CFG0_L1_MASK 0x70
#define ICM_CFG1_L2_MASK 0x07
#define ICM_CFG2_L3_MASK 0x70
#define ICM_CFG_SHIFT 4
/* Inter Connect Matrix allowed protocols */
#define ICM_PROTOCOL_PD 0x0
#define ICM_PROTOCOL_PCIE 0x1
#define ICM_PROTOCOL_SATA 0x2
#define ICM_PROTOCOL_USB 0x3
#define ICM_PROTOCOL_DP 0x4
#define ICM_PROTOCOL_SGMII 0x5
/* Test Mode common reset control parameters */
#define TM_CMN_RST 0x10018
#define TM_CMN_RST_EN 0x1
#define TM_CMN_RST_SET 0x2
#define TM_CMN_RST_MASK 0x3
/* Bus width parameters */
#define TX_PROT_BUS_WIDTH 0x10040
#define RX_PROT_BUS_WIDTH 0x10044
#define PROT_BUS_WIDTH_10 0x0
#define PROT_BUS_WIDTH_20 0x1
#define PROT_BUS_WIDTH_40 0x2
#define PROT_BUS_WIDTH_SHIFT 2
/* Number of GT lanes */
#define NUM_LANES 4
/* SIOU SATA control register */
#define SATA_CONTROL_OFFSET 0x0100
/* Total number of controllers */
#define CONTROLLERS_PER_LANE 5
/* Protocol Type parameters */
#define XPSGTR_TYPE_USB0 0 /* USB controller 0 */
#define XPSGTR_TYPE_USB1 1 /* USB controller 1 */
#define XPSGTR_TYPE_SATA_0 2 /* SATA controller lane 0 */
#define XPSGTR_TYPE_SATA_1 3 /* SATA controller lane 1 */
#define XPSGTR_TYPE_PCIE_0 4 /* PCIe controller lane 0 */
#define XPSGTR_TYPE_PCIE_1 5 /* PCIe controller lane 1 */
#define XPSGTR_TYPE_PCIE_2 6 /* PCIe controller lane 2 */
#define XPSGTR_TYPE_PCIE_3 7 /* PCIe controller lane 3 */
#define XPSGTR_TYPE_DP_0 8 /* Display Port controller lane 0 */
#define XPSGTR_TYPE_DP_1 9 /* Display Port controller lane 1 */
#define XPSGTR_TYPE_SGMII0 10 /* Ethernet SGMII controller 0 */
#define XPSGTR_TYPE_SGMII1 11 /* Ethernet SGMII controller 1 */
#define XPSGTR_TYPE_SGMII2 12 /* Ethernet SGMII controller 2 */
#define XPSGTR_TYPE_SGMII3 13 /* Ethernet SGMII controller 3 */
/* Timeout values */
#define TIMEOUT_US 1000
struct xpsgtr_dev;
/**
* struct xpsgtr_ssc - structure to hold SSC settings for a lane
* @refclk_rate: PLL reference clock frequency
* @pll_ref_clk: value to be written to register for corresponding ref clk rate
* @steps: number of steps of SSC (Spread Spectrum Clock)
* @step_size: step size of each step
*/
struct xpsgtr_ssc {
u32 refclk_rate;
u8 pll_ref_clk;
u32 steps;
u32 step_size;
};
/**
* struct xpsgtr_phy - representation of a lane
* @phy: pointer to the kernel PHY device
* @type: controller which uses this lane
* @lane: lane number
* @protocol: protocol in which the lane operates
* @skip_phy_init: skip phy_init() if true
* @dev: pointer to the xpsgtr_dev instance
* @refclk: reference clock index
*/
struct xpsgtr_phy {
struct phy *phy;
u8 type;
u8 lane;
u8 protocol;
bool skip_phy_init;
struct xpsgtr_dev *dev;
unsigned int refclk;
};
/**
* struct xpsgtr_dev - representation of a ZynMP GT device
* @dev: pointer to device
* @serdes: serdes base address
* @siou: siou base address
* @gtr_mutex: mutex for locking
* @phys: PHY lanes
* @refclk_sscs: spread spectrum settings for the reference clocks
* @tx_term_fix: fix for GT issue
* @saved_icm_cfg0: stored value of ICM CFG0 register
* @saved_icm_cfg1: stored value of ICM CFG1 register
*/
struct xpsgtr_dev {
struct device *dev;
void __iomem *serdes;
void __iomem *siou;
struct mutex gtr_mutex; /* mutex for locking */
struct xpsgtr_phy phys[NUM_LANES];
const struct xpsgtr_ssc *refclk_sscs[NUM_LANES];
bool tx_term_fix;
unsigned int saved_icm_cfg0;
unsigned int saved_icm_cfg1;
};
/*
* Configuration Data
*/
/* lookup table to hold all settings needed for a ref clock frequency */
static const struct xpsgtr_ssc ssc_lookup[] = {
{ 19200000, 0x05, 608, 264020 },
{ 20000000, 0x06, 634, 243454 },
{ 24000000, 0x07, 760, 168973 },
{ 26000000, 0x08, 824, 143860 },
{ 27000000, 0x09, 856, 86551 },
{ 38400000, 0x0a, 1218, 65896 },
{ 40000000, 0x0b, 634, 243454 },
{ 52000000, 0x0c, 824, 143860 },
{ 100000000, 0x0d, 1058, 87533 },
{ 108000000, 0x0e, 856, 86551 },
{ 125000000, 0x0f, 992, 119497 },
{ 135000000, 0x10, 1070, 55393 },
{ 150000000, 0x11, 792, 187091 }
};
/*
* I/O Accessors
*/
static inline u32 xpsgtr_read(struct xpsgtr_dev *gtr_dev, u32 reg)
{
return readl(gtr_dev->serdes + reg);
}
static inline void xpsgtr_write(struct xpsgtr_dev *gtr_dev, u32 reg, u32 value)
{
writel(value, gtr_dev->serdes + reg);
}
static inline void xpsgtr_clr_set(struct xpsgtr_dev *gtr_dev, u32 reg,
u32 clr, u32 set)
{
u32 value = xpsgtr_read(gtr_dev, reg);
value &= ~clr;
value |= set;
xpsgtr_write(gtr_dev, reg, value);
}
static inline u32 xpsgtr_read_phy(struct xpsgtr_phy *gtr_phy, u32 reg)
{
void __iomem *addr = gtr_phy->dev->serdes
+ gtr_phy->lane * PHY_REG_OFFSET + reg;
return readl(addr);
}
static inline void xpsgtr_write_phy(struct xpsgtr_phy *gtr_phy,
u32 reg, u32 value)
{
void __iomem *addr = gtr_phy->dev->serdes
+ gtr_phy->lane * PHY_REG_OFFSET + reg;
writel(value, addr);
}
static inline void xpsgtr_clr_set_phy(struct xpsgtr_phy *gtr_phy,
u32 reg, u32 clr, u32 set)
{
void __iomem *addr = gtr_phy->dev->serdes
+ gtr_phy->lane * PHY_REG_OFFSET + reg;
writel((readl(addr) & ~clr) | set, addr);
}
/*
* Hardware Configuration
*/
/* Wait for the PLL to lock (with a timeout). */
static int xpsgtr_wait_pll_lock(struct phy *phy)
{
struct xpsgtr_phy *gtr_phy = phy_get_drvdata(phy);
struct xpsgtr_dev *gtr_dev = gtr_phy->dev;
unsigned int timeout = TIMEOUT_US;
int ret;
dev_dbg(gtr_dev->dev, "Waiting for PLL lock\n");
while (1) {
u32 reg = xpsgtr_read_phy(gtr_phy, L0_PLL_STATUS_READ_1);
if ((reg & PLL_STATUS_LOCKED) == PLL_STATUS_LOCKED) {
ret = 0;
break;
}
if (--timeout == 0) {
ret = -ETIMEDOUT;
break;
}
udelay(1);
}
if (ret == -ETIMEDOUT)
dev_err(gtr_dev->dev,
"lane %u (type %u, protocol %u): PLL lock timeout\n",
gtr_phy->lane, gtr_phy->type, gtr_phy->protocol);
return ret;
}
/* Configure PLL and spread-sprectrum clock. */
static void xpsgtr_configure_pll(struct xpsgtr_phy *gtr_phy)
{
const struct xpsgtr_ssc *ssc;
u32 step_size;
ssc = gtr_phy->dev->refclk_sscs[gtr_phy->refclk];
step_size = ssc->step_size;
xpsgtr_clr_set(gtr_phy->dev, PLL_REF_SEL(gtr_phy->lane),
PLL_FREQ_MASK, ssc->pll_ref_clk);
/* Enable lane clock sharing, if required */
if (gtr_phy->refclk != gtr_phy->lane) {
/* Lane3 Ref Clock Selection Register */
xpsgtr_clr_set(gtr_phy->dev, L0_Ln_REF_CLK_SEL(gtr_phy->lane),
L0_REF_CLK_SEL_MASK, 1 << gtr_phy->refclk);
}
/* SSC step size [7:0] */
xpsgtr_clr_set_phy(gtr_phy, L0_PLL_SS_STEP_SIZE_0_LSB,
STEP_SIZE_0_MASK, step_size & STEP_SIZE_0_MASK);
/* SSC step size [15:8] */
step_size >>= STEP_SIZE_SHIFT;
xpsgtr_clr_set_phy(gtr_phy, L0_PLL_SS_STEP_SIZE_1,
STEP_SIZE_1_MASK, step_size & STEP_SIZE_1_MASK);
/* SSC step size [23:16] */
step_size >>= STEP_SIZE_SHIFT;
xpsgtr_clr_set_phy(gtr_phy, L0_PLL_SS_STEP_SIZE_2,
STEP_SIZE_2_MASK, step_size & STEP_SIZE_2_MASK);
/* SSC steps [7:0] */
xpsgtr_clr_set_phy(gtr_phy, L0_PLL_SS_STEPS_0_LSB,
STEPS_0_MASK, ssc->steps & STEPS_0_MASK);
/* SSC steps [10:8] */
xpsgtr_clr_set_phy(gtr_phy, L0_PLL_SS_STEPS_1_MSB,
STEPS_1_MASK,
(ssc->steps >> STEP_SIZE_SHIFT) & STEPS_1_MASK);
/* SSC step size [24:25] */
step_size >>= STEP_SIZE_SHIFT;
xpsgtr_clr_set_phy(gtr_phy, L0_PLL_SS_STEP_SIZE_3_MSB,
STEP_SIZE_3_MASK, (step_size & STEP_SIZE_3_MASK) |
FORCE_STEP_SIZE | FORCE_STEPS);
}
/* Configure the lane protocol. */
static void xpsgtr_lane_set_protocol(struct xpsgtr_phy *gtr_phy)
{
struct xpsgtr_dev *gtr_dev = gtr_phy->dev;
u8 protocol = gtr_phy->protocol;
switch (gtr_phy->lane) {
case 0:
xpsgtr_clr_set(gtr_dev, ICM_CFG0, ICM_CFG0_L0_MASK, protocol);
break;
case 1:
xpsgtr_clr_set(gtr_dev, ICM_CFG0, ICM_CFG0_L1_MASK,
protocol << ICM_CFG_SHIFT);
break;
case 2:
xpsgtr_clr_set(gtr_dev, ICM_CFG1, ICM_CFG0_L0_MASK, protocol);
break;
case 3:
xpsgtr_clr_set(gtr_dev, ICM_CFG1, ICM_CFG0_L1_MASK,
protocol << ICM_CFG_SHIFT);
break;
default:
/* We already checked 0 <= lane <= 3 */
break;
}
}
/* Bypass (de)scrambler and 8b/10b decoder and encoder. */
static void xpsgtr_bypass_scrambler_8b10b(struct xpsgtr_phy *gtr_phy)
{
xpsgtr_write_phy(gtr_phy, L0_TM_DIG_6, L0_TM_DIS_DESCRAMBLE_DECODER);
xpsgtr_write_phy(gtr_phy, L0_TX_DIG_61, L0_TM_DISABLE_SCRAMBLE_ENCODER);
}
/* DP-specific initialization. */
static void xpsgtr_phy_init_dp(struct xpsgtr_phy *gtr_phy)
{
xpsgtr_write_phy(gtr_phy, L0_TXPMD_TM_45,
L0_TXPMD_TM_45_OVER_DP_MAIN |
L0_TXPMD_TM_45_ENABLE_DP_MAIN |
L0_TXPMD_TM_45_OVER_DP_POST1 |
L0_TXPMD_TM_45_OVER_DP_POST2 |
L0_TXPMD_TM_45_ENABLE_DP_POST2);
xpsgtr_write_phy(gtr_phy, L0_TX_ANA_TM_118,
L0_TX_ANA_TM_118_FORCE_17_0);
}
/* SATA-specific initialization. */
static void xpsgtr_phy_init_sata(struct xpsgtr_phy *gtr_phy)
{
struct xpsgtr_dev *gtr_dev = gtr_phy->dev;
xpsgtr_bypass_scrambler_8b10b(gtr_phy);
writel(gtr_phy->lane, gtr_dev->siou + SATA_CONTROL_OFFSET);
}
/* SGMII-specific initialization. */
static void xpsgtr_phy_init_sgmii(struct xpsgtr_phy *gtr_phy)
{
struct xpsgtr_dev *gtr_dev = gtr_phy->dev;
/* Set SGMII protocol TX and RX bus width to 10 bits. */
xpsgtr_write(gtr_dev, TX_PROT_BUS_WIDTH,
PROT_BUS_WIDTH_10 << (gtr_phy->lane * PROT_BUS_WIDTH_SHIFT));
xpsgtr_write(gtr_dev, RX_PROT_BUS_WIDTH,
PROT_BUS_WIDTH_10 << (gtr_phy->lane * PROT_BUS_WIDTH_SHIFT));
xpsgtr_bypass_scrambler_8b10b(gtr_phy);
}
/* Configure TX de-emphasis and margining for DP. */
static void xpsgtr_phy_configure_dp(struct xpsgtr_phy *gtr_phy, unsigned int pre,
unsigned int voltage)
{
static const u8 voltage_swing[4][4] = {
{ 0x2a, 0x27, 0x24, 0x20 },
{ 0x27, 0x23, 0x20, 0xff },
{ 0x24, 0x20, 0xff, 0xff },
{ 0xff, 0xff, 0xff, 0xff }
};
static const u8 pre_emphasis[4][4] = {
{ 0x02, 0x02, 0x02, 0x02 },
{ 0x01, 0x01, 0x01, 0xff },
{ 0x00, 0x00, 0xff, 0xff },
{ 0xff, 0xff, 0xff, 0xff }
};
xpsgtr_write_phy(gtr_phy, L0_TXPMD_TM_48, voltage_swing[pre][voltage]);
xpsgtr_write_phy(gtr_phy, L0_TX_ANA_TM_18, pre_emphasis[pre][voltage]);
}
/*
* PHY Operations
*/
static bool xpsgtr_phy_init_required(struct xpsgtr_phy *gtr_phy)
{
/*
* As USB may save the snapshot of the states during hibernation, doing
* phy_init() will put the USB controller into reset, resulting in the
* losing of the saved snapshot. So try to avoid phy_init() for USB
* except when gtr_phy->skip_phy_init is false (this happens when FPD is
* shutdown during suspend or when gt lane is changed from current one)
*/
if (gtr_phy->protocol == ICM_PROTOCOL_USB && gtr_phy->skip_phy_init)
return false;
else
return true;
}
/*
* There is a functional issue in the GT. The TX termination resistance can be
* out of spec due to a issue in the calibration logic. This is the workaround
* to fix it, required for XCZU9EG silicon.
*/
static int xpsgtr_phy_tx_term_fix(struct xpsgtr_phy *gtr_phy)
{
struct xpsgtr_dev *gtr_dev = gtr_phy->dev;
u32 timeout = TIMEOUT_US;
u32 nsw;
/* Enabling Test Mode control for CMN Rest */
xpsgtr_clr_set(gtr_dev, TM_CMN_RST, TM_CMN_RST_MASK, TM_CMN_RST_SET);
/* Set Test Mode reset */
xpsgtr_clr_set(gtr_dev, TM_CMN_RST, TM_CMN_RST_MASK, TM_CMN_RST_EN);
xpsgtr_write(gtr_dev, L3_TM_CALIB_DIG18, 0x00);
xpsgtr_write(gtr_dev, L3_TM_CALIB_DIG19, L3_TM_OVERRIDE_NSW_CODE);
/*
* As a part of work around sequence for PMOS calibration fix,
* we need to configure any lane ICM_CFG to valid protocol. This
* will deassert the CMN_Resetn signal.
*/
xpsgtr_lane_set_protocol(gtr_phy);
/* Clear Test Mode reset */
xpsgtr_clr_set(gtr_dev, TM_CMN_RST, TM_CMN_RST_MASK, TM_CMN_RST_SET);
dev_dbg(gtr_dev->dev, "calibrating...\n");
do {
u32 reg = xpsgtr_read(gtr_dev, L3_CALIB_DONE_STATUS);
if ((reg & L3_CALIB_DONE) == L3_CALIB_DONE)
break;
if (!--timeout) {
dev_err(gtr_dev->dev, "calibration time out\n");
return -ETIMEDOUT;
}
udelay(1);
} while (timeout > 0);
dev_dbg(gtr_dev->dev, "calibration done\n");
/* Reading NMOS Register Code */
nsw = xpsgtr_read(gtr_dev, L0_TXPMA_ST_3) & L0_DN_CALIB_CODE;
/* Set Test Mode reset */
xpsgtr_clr_set(gtr_dev, TM_CMN_RST, TM_CMN_RST_MASK, TM_CMN_RST_EN);
/* Writing NMOS register values back [5:3] */
xpsgtr_write(gtr_dev, L3_TM_CALIB_DIG19, nsw >> L3_NSW_CALIB_SHIFT);
/* Writing NMOS register value [2:0] */
xpsgtr_write(gtr_dev, L3_TM_CALIB_DIG18,
((nsw & L3_TM_CALIB_DIG19_NSW) << L3_NSW_SHIFT) |
(1 << L3_NSW_PIPE_SHIFT));
/* Clear Test Mode reset */
xpsgtr_clr_set(gtr_dev, TM_CMN_RST, TM_CMN_RST_MASK, TM_CMN_RST_SET);
return 0;
}
static int xpsgtr_phy_init(struct phy *phy)
{
struct xpsgtr_phy *gtr_phy = phy_get_drvdata(phy);
struct xpsgtr_dev *gtr_dev = gtr_phy->dev;
int ret = 0;
mutex_lock(&gtr_dev->gtr_mutex);
/* Skip initialization if not required. */
if (!xpsgtr_phy_init_required(gtr_phy))
goto out;
if (gtr_dev->tx_term_fix) {
ret = xpsgtr_phy_tx_term_fix(gtr_phy);
if (ret < 0)
goto out;
gtr_dev->tx_term_fix = false;
}
/* Enable coarse code saturation limiting logic. */
xpsgtr_write_phy(gtr_phy, L0_TM_PLL_DIG_37, L0_TM_COARSE_CODE_LIMIT);
/*
* Configure the PLL, the lane protocol, and perform protocol-specific
* initialization.
*/
xpsgtr_configure_pll(gtr_phy);
xpsgtr_lane_set_protocol(gtr_phy);
switch (gtr_phy->protocol) {
case ICM_PROTOCOL_DP:
xpsgtr_phy_init_dp(gtr_phy);
break;
case ICM_PROTOCOL_SATA:
xpsgtr_phy_init_sata(gtr_phy);
break;
case ICM_PROTOCOL_SGMII:
xpsgtr_phy_init_sgmii(gtr_phy);
break;
}
out:
mutex_unlock(&gtr_dev->gtr_mutex);
return ret;
}
static int xpsgtr_phy_exit(struct phy *phy)
{
struct xpsgtr_phy *gtr_phy = phy_get_drvdata(phy);
gtr_phy->skip_phy_init = false;
return 0;
}
static int xpsgtr_phy_power_on(struct phy *phy)
{
struct xpsgtr_phy *gtr_phy = phy_get_drvdata(phy);
int ret = 0;
/*
* Wait for the PLL to lock. For DP, only wait on DP0 to avoid
* cumulating waits for both lanes. The user is expected to initialize
* lane 0 last.
*/
if (gtr_phy->protocol != ICM_PROTOCOL_DP ||
gtr_phy->type == XPSGTR_TYPE_DP_0)
ret = xpsgtr_wait_pll_lock(phy);
return ret;
}
static int xpsgtr_phy_configure(struct phy *phy, union phy_configure_opts *opts)
{
struct xpsgtr_phy *gtr_phy = phy_get_drvdata(phy);
if (gtr_phy->protocol != ICM_PROTOCOL_DP)
return 0;
xpsgtr_phy_configure_dp(gtr_phy, opts->dp.pre[0], opts->dp.voltage[0]);
return 0;
}
static const struct phy_ops xpsgtr_phyops = {
.init = xpsgtr_phy_init,
.exit = xpsgtr_phy_exit,
.power_on = xpsgtr_phy_power_on,
.configure = xpsgtr_phy_configure,
.owner = THIS_MODULE,
};
/*
* OF Xlate Support
*/
/* Set the lane type and protocol based on the PHY type and instance number. */
static int xpsgtr_set_lane_type(struct xpsgtr_phy *gtr_phy, u8 phy_type,
unsigned int phy_instance)
{
unsigned int num_phy_types;
const int *phy_types;
switch (phy_type) {
case PHY_TYPE_SATA: {
static const int types[] = {
XPSGTR_TYPE_SATA_0,
XPSGTR_TYPE_SATA_1,
};
phy_types = types;
num_phy_types = ARRAY_SIZE(types);
gtr_phy->protocol = ICM_PROTOCOL_SATA;
break;
}
case PHY_TYPE_USB3: {
static const int types[] = {
XPSGTR_TYPE_USB0,
XPSGTR_TYPE_USB1,
};
phy_types = types;
num_phy_types = ARRAY_SIZE(types);
gtr_phy->protocol = ICM_PROTOCOL_USB;
break;
}
case PHY_TYPE_DP: {
static const int types[] = {
XPSGTR_TYPE_DP_0,
XPSGTR_TYPE_DP_1,
};
phy_types = types;
num_phy_types = ARRAY_SIZE(types);
gtr_phy->protocol = ICM_PROTOCOL_DP;
break;
}
case PHY_TYPE_PCIE: {
static const int types[] = {
XPSGTR_TYPE_PCIE_0,
XPSGTR_TYPE_PCIE_1,
XPSGTR_TYPE_PCIE_2,
XPSGTR_TYPE_PCIE_3,
};
phy_types = types;
num_phy_types = ARRAY_SIZE(types);
gtr_phy->protocol = ICM_PROTOCOL_PCIE;
break;
}
case PHY_TYPE_SGMII: {
static const int types[] = {
XPSGTR_TYPE_SGMII0,
XPSGTR_TYPE_SGMII1,
XPSGTR_TYPE_SGMII2,
XPSGTR_TYPE_SGMII3,
};
phy_types = types;
num_phy_types = ARRAY_SIZE(types);
gtr_phy->protocol = ICM_PROTOCOL_SGMII;
break;
}
default:
return -EINVAL;
}
if (phy_instance >= num_phy_types)
return -EINVAL;
gtr_phy->type = phy_types[phy_instance];
return 0;
}
/*
* Valid combinations of controllers and lanes (Interconnect Matrix).
*/
static const unsigned int icm_matrix[NUM_LANES][CONTROLLERS_PER_LANE] = {
{ XPSGTR_TYPE_PCIE_0, XPSGTR_TYPE_SATA_0, XPSGTR_TYPE_USB0,
XPSGTR_TYPE_DP_1, XPSGTR_TYPE_SGMII0 },
{ XPSGTR_TYPE_PCIE_1, XPSGTR_TYPE_SATA_1, XPSGTR_TYPE_USB0,
XPSGTR_TYPE_DP_0, XPSGTR_TYPE_SGMII1 },
{ XPSGTR_TYPE_PCIE_2, XPSGTR_TYPE_SATA_0, XPSGTR_TYPE_USB0,
XPSGTR_TYPE_DP_1, XPSGTR_TYPE_SGMII2 },
{ XPSGTR_TYPE_PCIE_3, XPSGTR_TYPE_SATA_1, XPSGTR_TYPE_USB1,
XPSGTR_TYPE_DP_0, XPSGTR_TYPE_SGMII3 }
};
/* Translate OF phandle and args to PHY instance. */
static struct phy *xpsgtr_xlate(struct device *dev,
struct of_phandle_args *args)
{
struct xpsgtr_dev *gtr_dev = dev_get_drvdata(dev);
struct xpsgtr_phy *gtr_phy;
unsigned int phy_instance;
unsigned int phy_lane;
unsigned int phy_type;
unsigned int refclk;
unsigned int i;
int ret;
if (args->args_count != 4) {
dev_err(dev, "Invalid number of cells in 'phy' property\n");
return ERR_PTR(-EINVAL);
}
/*
* Get the PHY parameters from the OF arguments and derive the lane
* type.
*/
phy_lane = args->args[0];
if (phy_lane >= ARRAY_SIZE(gtr_dev->phys)) {
dev_err(dev, "Invalid lane number %u\n", phy_lane);
return ERR_PTR(-ENODEV);
}
gtr_phy = &gtr_dev->phys[phy_lane];
phy_type = args->args[1];
phy_instance = args->args[2];
ret = xpsgtr_set_lane_type(gtr_phy, phy_type, phy_instance);
if (ret < 0) {
dev_err(gtr_dev->dev, "Invalid PHY type and/or instance\n");
return ERR_PTR(ret);
}
refclk = args->args[3];
if (refclk >= ARRAY_SIZE(gtr_dev->refclk_sscs) ||
!gtr_dev->refclk_sscs[refclk]) {
dev_err(dev, "Invalid reference clock number %u\n", refclk);
return ERR_PTR(-EINVAL);
}
gtr_phy->refclk = refclk;
/*
* Ensure that the Interconnect Matrix is obeyed, i.e a given lane type
* is allowed to operate on the lane.
*/
for (i = 0; i < CONTROLLERS_PER_LANE; i++) {
if (icm_matrix[phy_lane][i] == gtr_phy->type)
return gtr_phy->phy;
}
return ERR_PTR(-EINVAL);
}
/*
* Power Management
*/
static int __maybe_unused xpsgtr_suspend(struct device *dev)
{
struct xpsgtr_dev *gtr_dev = dev_get_drvdata(dev);
/* Save the snapshot ICM_CFG registers. */
gtr_dev->saved_icm_cfg0 = xpsgtr_read(gtr_dev, ICM_CFG0);
gtr_dev->saved_icm_cfg1 = xpsgtr_read(gtr_dev, ICM_CFG1);
return 0;
}
static int __maybe_unused xpsgtr_resume(struct device *dev)
{
struct xpsgtr_dev *gtr_dev = dev_get_drvdata(dev);
unsigned int icm_cfg0, icm_cfg1;
unsigned int i;
bool skip_phy_init;
icm_cfg0 = xpsgtr_read(gtr_dev, ICM_CFG0);
icm_cfg1 = xpsgtr_read(gtr_dev, ICM_CFG1);
/* Return if no GT lanes got configured before suspend. */
if (!gtr_dev->saved_icm_cfg0 && !gtr_dev->saved_icm_cfg1)
return 0;
/* Check if the ICM configurations changed after suspend. */
if (icm_cfg0 == gtr_dev->saved_icm_cfg0 &&
icm_cfg1 == gtr_dev->saved_icm_cfg1)
skip_phy_init = true;
else
skip_phy_init = false;
/* Update the skip_phy_init for all gtr_phy instances. */
for (i = 0; i < ARRAY_SIZE(gtr_dev->phys); i++)
gtr_dev->phys[i].skip_phy_init = skip_phy_init;
return 0;
}
static const struct dev_pm_ops xpsgtr_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(xpsgtr_suspend, xpsgtr_resume)
};
/*
* Probe & Platform Driver
*/
static int xpsgtr_get_ref_clocks(struct xpsgtr_dev *gtr_dev)
{
unsigned int refclk;
for (refclk = 0; refclk < ARRAY_SIZE(gtr_dev->refclk_sscs); ++refclk) {
unsigned long rate;
unsigned int i;
struct clk *clk;
char name[8];
snprintf(name, sizeof(name), "ref%u", refclk);
clk = devm_clk_get_optional(gtr_dev->dev, name);
if (IS_ERR(clk)) {
if (PTR_ERR(clk) != -EPROBE_DEFER)
dev_err(gtr_dev->dev,
"Failed to get reference clock %u: %ld\n",
refclk, PTR_ERR(clk));
return PTR_ERR(clk);
}
if (!clk)
continue;
/*
* Get the spread spectrum (SSC) settings for the reference
* clock rate.
*/
rate = clk_get_rate(clk);
for (i = 0 ; i < ARRAY_SIZE(ssc_lookup); i++) {
if (rate == ssc_lookup[i].refclk_rate) {
gtr_dev->refclk_sscs[refclk] = &ssc_lookup[i];
break;
}
}
if (i == ARRAY_SIZE(ssc_lookup)) {
dev_err(gtr_dev->dev,
"Invalid rate %lu for reference clock %u\n",
rate, refclk);
return -EINVAL;
}
}
return 0;
}
static int xpsgtr_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct xpsgtr_dev *gtr_dev;
struct phy_provider *provider;
unsigned int port;
int ret;
gtr_dev = devm_kzalloc(&pdev->dev, sizeof(*gtr_dev), GFP_KERNEL);
if (!gtr_dev)
return -ENOMEM;
gtr_dev->dev = &pdev->dev;
platform_set_drvdata(pdev, gtr_dev);
mutex_init(&gtr_dev->gtr_mutex);
if (of_device_is_compatible(np, "xlnx,zynqmp-psgtr"))
gtr_dev->tx_term_fix =
of_property_read_bool(np, "xlnx,tx-termination-fix");
/* Acquire resources. */
gtr_dev->serdes = devm_platform_ioremap_resource_byname(pdev, "serdes");
if (IS_ERR(gtr_dev->serdes))
return PTR_ERR(gtr_dev->serdes);
gtr_dev->siou = devm_platform_ioremap_resource_byname(pdev, "siou");
if (IS_ERR(gtr_dev->siou))
return PTR_ERR(gtr_dev->siou);
ret = xpsgtr_get_ref_clocks(gtr_dev);
if (ret)
return ret;
/* Create PHYs. */
for (port = 0; port < ARRAY_SIZE(gtr_dev->phys); ++port) {
struct xpsgtr_phy *gtr_phy = &gtr_dev->phys[port];
struct phy *phy;
gtr_phy->lane = port;
gtr_phy->dev = gtr_dev;
phy = devm_phy_create(&pdev->dev, np, &xpsgtr_phyops);
if (IS_ERR(phy)) {
dev_err(&pdev->dev, "failed to create PHY\n");
return PTR_ERR(phy);
}
gtr_phy->phy = phy;
phy_set_drvdata(phy, gtr_phy);
}
/* Register the PHY provider. */
provider = devm_of_phy_provider_register(&pdev->dev, xpsgtr_xlate);
if (IS_ERR(provider)) {
dev_err(&pdev->dev, "registering provider failed\n");
return PTR_ERR(provider);
}
return 0;
}
static const struct of_device_id xpsgtr_of_match[] = {
{ .compatible = "xlnx,zynqmp-psgtr", },
{ .compatible = "xlnx,zynqmp-psgtr-v1.1", },
{},
};
MODULE_DEVICE_TABLE(of, xpsgtr_of_match);
static struct platform_driver xpsgtr_driver = {
.probe = xpsgtr_probe,
.driver = {
.name = "xilinx-psgtr",
.of_match_table = xpsgtr_of_match,
.pm = &xpsgtr_pm_ops,
},
};
module_platform_driver(xpsgtr_driver);
MODULE_AUTHOR("Xilinx Inc.");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Xilinx ZynqMP High speed Gigabit Transceiver");

View File

@ -18,5 +18,6 @@
#define PHY_TYPE_UFS 5
#define PHY_TYPE_DP 6
#define PHY_TYPE_XPCS 7
#define PHY_TYPE_SGMII 8
#endif /* _DT_BINDINGS_PHY */