diff options
author | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2017-10-27 11:52:46 +0200 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2017-10-27 11:52:46 +0200 |
commit | 6bd5bb1ede67a9681d975c46c55bd8ebaa0926da (patch) | |
tree | 26543421726c5911bfd39e05095ab27cf6ad081b | |
parent | c929d84735bff4349bde809a5a8eb1996bf371f6 (diff) | |
parent | 36914111e6829be36b23d1109214250b5ee1ee9c (diff) | |
download | linux-6bd5bb1ede67a9681d975c46c55bd8ebaa0926da.tar.bz2 |
Merge tag 'phy-for-4.15_v1' of git://git.kernel.org/pub/scm/linux/kernel/git/kishon/linux-phy into usb-next
Kishon writes:
phy: for 4.15
*) Add support in phy core to perform phy calibration
*) Return NULL for optional PHY's even if CONFIG_GENERIC_PHY is not selected
*) Add USB Phy driver for Broadcom STB SoCs
*) Add support to force mediatek PHY with USB OTG function to enter
a specific mode
*) Calibrate rockchip-typec PHY according to docs
*) Enable dual route feature for sun4i-usb in V3s SoC
*) Use dr_mode dt property to enable otg capability in rcar-gen3-usb2
*) Add driver data to specify dedicated otg pins in rcar-gen3-usb2 driver
*) Configure the RX equalizer of brcm-sata PHY
*) Update pcie phy settings for ti-pipe3 phy
*) Add set_mode callback in qcom-ufs-qmp-14nm phy
*) Use PHY callbacks in phy-qcom-ufs instead of export APIs
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
29 files changed, 2205 insertions, 132 deletions
diff --git a/Documentation/devicetree/bindings/phy/brcm,brcmstb-usb-phy.txt b/Documentation/devicetree/bindings/phy/brcm,brcmstb-usb-phy.txt new file mode 100644 index 000000000000..24a0d06acd1d --- /dev/null +++ b/Documentation/devicetree/bindings/phy/brcm,brcmstb-usb-phy.txt @@ -0,0 +1,43 @@ +Broadcom STB USB PHY + +Required properties: + - compatible: brcm,brcmstb-usb-phy + - reg: two offset and length pairs. + The first pair specifies a manditory set of memory mapped + registers used for general control of the PHY. + The second pair specifies optional registers used by some of + the SoCs that support USB 3.x + - #phy-cells: Shall be 1 as it expects one argument for setting + the type of the PHY. Possible values are: + - PHY_TYPE_USB2 for USB1.1/2.0 PHY + - PHY_TYPE_USB3 for USB3.x PHY + +Optional Properties: +- clocks : clock phandles. +- clock-names: String, clock name. +- brcm,ipp: Boolean, Invert Port Power. + Possible values are: 0 (Don't invert), 1 (Invert) +- brcm,ioc: Boolean, Invert Over Current detection. + Possible values are: 0 (Don't invert), 1 (Invert) +NOTE: one or both of the following two properties must be set +- brcm,has-xhci: Boolean indicating the phy has an XHCI phy. +- brcm,has-eohci: Boolean indicating the phy has an EHCI/OHCI phy. +- dr_mode: String, PHY Device mode. + Possible values are: "host", "peripheral ", "drd" or "typec-pd" + If this property is not defined, the phy will default to "host" mode. + +Example: + +usbphy_0: usb-phy@f0470200 { + reg = <0xf0470200 0xb8>, + <0xf0471940 0x6c0>; + compatible = "brcm,brcmstb-usb-phy"; + #phy-cells = <1>; + dr_mode = "host" + brcm,ioc = <1>; + brcm,ipp = <1>; + brcm,has-xhci; + brcm,has-eohci; + clocks = <&usb20>, <&usb30>; + clock-names = "sw_usb", "sw_usb3"; +}; diff --git a/Documentation/devicetree/bindings/phy/brcm-sata-phy.txt b/Documentation/devicetree/bindings/phy/brcm-sata-phy.txt index 97977cd29a98..0aced97d8092 100644 --- a/Documentation/devicetree/bindings/phy/brcm-sata-phy.txt +++ b/Documentation/devicetree/bindings/phy/brcm-sata-phy.txt @@ -27,7 +27,16 @@ Sub-nodes optional properties: This property is not applicable for "brcm,iproc-ns2-sata-phy", "brcm,iproc-nsp-sata-phy" and "brcm,iproc-sr-sata-phy". -Example: +- brcm,rxaeq-mode: string that indicates the desired RX equalizer + mode, possible values are: + "off" (equivalent to not specifying the property) + "auto" + "manual" (brcm,rxaeq-value is used in that case) + +- brcm,rxaeq-value: when 'rxaeq-mode' is set to "manual", provides the RX + equalizer value that should be used. Allowed range is 0..63. + +Example sata-phy@f0458100 { compatible = "brcm,bcm7445-sata-phy", "brcm,phy-sata3"; reg = <0xf0458100 0x1e00>, <0xf045804c 0x10>; diff --git a/Documentation/devicetree/bindings/phy/rcar-gen2-phy.txt b/Documentation/devicetree/bindings/phy/rcar-gen2-phy.txt index 91da947ae9b6..eeb9e1874ea6 100644 --- a/Documentation/devicetree/bindings/phy/rcar-gen2-phy.txt +++ b/Documentation/devicetree/bindings/phy/rcar-gen2-phy.txt @@ -4,10 +4,13 @@ This file provides information on what the device node for the R-Car generation 2 USB PHY contains. Required properties: -- compatible: "renesas,usb-phy-r8a7790" if the device is a part of R8A7790 SoC. +- compatible: "renesas,usb-phy-r8a7743" if the device is a part of R8A7743 SoC. + "renesas,usb-phy-r8a7745" if the device is a part of R8A7745 SoC. + "renesas,usb-phy-r8a7790" if the device is a part of R8A7790 SoC. "renesas,usb-phy-r8a7791" if the device is a part of R8A7791 SoC. "renesas,usb-phy-r8a7794" if the device is a part of R8A7794 SoC. - "renesas,rcar-gen2-usb-phy" for a generic R-Car Gen2 compatible device. + "renesas,rcar-gen2-usb-phy" for a generic R-Car Gen2 or + RZ/G1 compatible device. When compatible with the generic version, nodes must list the SoC-specific version corresponding to the platform first diff --git a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt index ace9cce2704a..99b651b33110 100644 --- a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt +++ b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt @@ -8,6 +8,8 @@ Required properties: SoC. "renesas,usb2-phy-r8a7796" if the device is a part of an R8A7796 SoC. + "renesas,usb2-phy-r8a77995" if the device is a part of an + R8A77995 SoC. "renesas,rcar-gen3-usb2-phy" for a generic R-Car Gen3 compatible device. When compatible with the generic version, nodes must list the diff --git a/MAINTAINERS b/MAINTAINERS index d85c08956875..c83a698f4c98 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2896,6 +2896,13 @@ S: Supported F: drivers/gpio/gpio-brcmstb.c F: Documentation/devicetree/bindings/gpio/brcm,brcmstb-gpio.txt +BROADCOM BRCMSTB USB2 and USB3 PHY DRIVER +M: Al Cooper <alcooperx@gmail.com> +L: linux-kernel@vger.kernel.org +L: bcm-kernel-feedback-list@broadcom.com +S: Maintained +F: drivers/phy/broadcom/phy-brcm-usb* + BROADCOM GENET ETHERNET DRIVER M: Florian Fainelli <f.fainelli@gmail.com> L: netdev@vger.kernel.org diff --git a/drivers/pci/dwc/pci-dra7xx.c b/drivers/pci/dwc/pci-dra7xx.c index 34427a6a15af..362607f727ee 100644 --- a/drivers/pci/dwc/pci-dra7xx.c +++ b/drivers/pci/dwc/pci-dra7xx.c @@ -11,6 +11,7 @@ */ #include <linux/delay.h> +#include <linux/device.h> #include <linux/err.h> #include <linux/interrupt.h> #include <linux/irq.h> @@ -594,6 +595,7 @@ static int __init dra7xx_pcie_probe(struct platform_device *pdev) int i; int phy_count; struct phy **phy; + struct device_link **link; void __iomem *base; struct resource *res; struct dw_pcie *pci; @@ -649,11 +651,21 @@ static int __init dra7xx_pcie_probe(struct platform_device *pdev) if (!phy) return -ENOMEM; + link = devm_kzalloc(dev, sizeof(*link) * phy_count, GFP_KERNEL); + if (!link) + return -ENOMEM; + for (i = 0; i < phy_count; i++) { snprintf(name, sizeof(name), "pcie-phy%d", i); phy[i] = devm_phy_get(dev, name); if (IS_ERR(phy[i])) return PTR_ERR(phy[i]); + + link[i] = device_link_add(dev, &phy[i]->dev, DL_FLAG_STATELESS); + if (!link[i]) { + ret = -EINVAL; + goto err_link; + } } dra7xx->base = base; @@ -732,6 +744,10 @@ err_get_sync: pm_runtime_disable(dev); dra7xx_pcie_disable_phy(dra7xx); +err_link: + while (--i >= 0) + device_link_del(link[i]); + return ret; } diff --git a/drivers/phy/allwinner/phy-sun4i-usb.c b/drivers/phy/allwinner/phy-sun4i-usb.c index 1161e11fb3cf..a2fbc7b00b9a 100644 --- a/drivers/phy/allwinner/phy-sun4i-usb.c +++ b/drivers/phy/allwinner/phy-sun4i-usb.c @@ -926,6 +926,7 @@ static const struct sun4i_usb_phy_cfg sun8i_v3s_cfg = { .phyctl_offset = REG_PHYCTL_A33, .dedicated_clocks = true, .enable_pmu_unk1 = true, + .phy0_dual_route = true, }; static const struct sun4i_usb_phy_cfg sun50i_a64_cfg = { diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig index 64fc59c3ae6d..97d27b0d5cc7 100644 --- a/drivers/phy/broadcom/Kconfig +++ b/drivers/phy/broadcom/Kconfig @@ -67,3 +67,16 @@ config PHY_BRCM_SATA help Enable this to support the Broadcom SATA PHY. If unsure, say N. + +config PHY_BRCM_USB + tristate "Broadcom STB USB PHY driver" + depends on ARCH_BRCMSTB + depends on OF + select GENERIC_PHY + select SOC_BRCMSTB + default ARCH_BRCMSTB + help + Enable this to support the Broadcom STB USB PHY. + This driver is required by the USB XHCI, EHCI and OHCI + drivers. + If unsure, say N. diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile index 4eb82ec8d491..7c37ba651440 100644 --- a/drivers/phy/broadcom/Makefile +++ b/drivers/phy/broadcom/Makefile @@ -5,3 +5,6 @@ obj-$(CONFIG_PHY_BCM_NS_USB3) += phy-bcm-ns-usb3.o obj-$(CONFIG_PHY_NS2_PCIE) += phy-bcm-ns2-pcie.o obj-$(CONFIG_PHY_NS2_USB_DRD) += phy-bcm-ns2-usbdrd.o obj-$(CONFIG_PHY_BRCM_SATA) += phy-brcm-sata.o +obj-$(CONFIG_PHY_BRCM_USB) += phy-brcm-usb-dvr.o + +phy-brcm-usb-dvr-objs := phy-brcm-usb.o phy-brcm-usb-init.o diff --git a/drivers/phy/broadcom/phy-brcm-sata.c b/drivers/phy/broadcom/phy-brcm-sata.c index 9d7f74fe3d7c..3f953db70288 100644 --- a/drivers/phy/broadcom/phy-brcm-sata.c +++ b/drivers/phy/broadcom/phy-brcm-sata.c @@ -49,11 +49,29 @@ enum brcm_sata_phy_version { BRCM_SATA_PHY_IPROC_SR, }; +enum brcm_sata_phy_rxaeq_mode { + RXAEQ_MODE_OFF = 0, + RXAEQ_MODE_AUTO, + RXAEQ_MODE_MANUAL, +}; + +static enum brcm_sata_phy_rxaeq_mode rxaeq_to_val(const char *m) +{ + if (!strcmp(m, "auto")) + return RXAEQ_MODE_AUTO; + else if (!strcmp(m, "manual")) + return RXAEQ_MODE_MANUAL; + else + return RXAEQ_MODE_OFF; +} + struct brcm_sata_port { int portnum; struct phy *phy; struct brcm_sata_phy *phy_priv; bool ssc_en; + enum brcm_sata_phy_rxaeq_mode rxaeq_mode; + u32 rxaeq_val; }; struct brcm_sata_phy { @@ -93,6 +111,15 @@ enum sata_phy_regs { TX_ACTRL0 = 0x80, TX_ACTRL0_TXPOL_FLIP = BIT(6), + AEQRX_REG_BANK_0 = 0xd0, + AEQ_CONTROL1 = 0x81, + AEQ_CONTROL1_ENABLE = BIT(2), + AEQ_CONTROL1_FREEZE = BIT(3), + AEQ_FRC_EQ = 0x83, + AEQ_FRC_EQ_FORCE = BIT(0), + AEQ_FRC_EQ_FORCE_VAL = BIT(1), + AEQRX_REG_BANK_1 = 0xe0, + OOB_REG_BANK = 0x150, OOB1_REG_BANK = 0x160, OOB_CTRL1 = 0x80, @@ -190,7 +217,7 @@ static u32 brcm_sata_phy_rd(void __iomem *pcb_base, u32 bank, u32 ofs) #define STB_FMAX_VAL_DEFAULT 0x3df #define STB_FMAX_VAL_SSC 0x83 -static int brcm_stb_sata_init(struct brcm_sata_port *port) +static void brcm_stb_sata_ssc_init(struct brcm_sata_port *port) { void __iomem *base = brcm_sata_pcb_base(port); struct brcm_sata_phy *priv = port->phy_priv; @@ -215,10 +242,47 @@ static int brcm_stb_sata_init(struct brcm_sata_port *port) brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL3, ~TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK, tmp); +} + +#define AEQ_FRC_EQ_VAL_SHIFT 2 +#define AEQ_FRC_EQ_VAL_MASK 0x3f + +static int brcm_stb_sata_rxaeq_init(struct brcm_sata_port *port) +{ + void __iomem *base = brcm_sata_pcb_base(port); + u32 tmp = 0, reg = 0; + + switch (port->rxaeq_mode) { + case RXAEQ_MODE_OFF: + return 0; + + case RXAEQ_MODE_AUTO: + reg = AEQ_CONTROL1; + tmp = AEQ_CONTROL1_ENABLE | AEQ_CONTROL1_FREEZE; + break; + + case RXAEQ_MODE_MANUAL: + reg = AEQ_FRC_EQ; + tmp = AEQ_FRC_EQ_FORCE | AEQ_FRC_EQ_FORCE_VAL; + if (port->rxaeq_val > AEQ_FRC_EQ_VAL_MASK) + return -EINVAL; + tmp |= port->rxaeq_val << AEQ_FRC_EQ_VAL_SHIFT; + break; + } + + brcm_sata_phy_wr(base, AEQRX_REG_BANK_0, reg, ~tmp, tmp); + brcm_sata_phy_wr(base, AEQRX_REG_BANK_1, reg, ~tmp, tmp); return 0; } +static int brcm_stb_sata_init(struct brcm_sata_port *port) +{ + brcm_stb_sata_ssc_init(port); + + return brcm_stb_sata_rxaeq_init(port); +} + /* NS2 SATA PLL1 defaults were characterized by H/W group */ #define NS2_PLL1_ACTRL2_MAGIC 0x1df8 #define NS2_PLL1_ACTRL3_MAGIC 0x2b00 @@ -463,6 +527,7 @@ MODULE_DEVICE_TABLE(of, brcm_sata_phy_of_match); static int brcm_sata_phy_probe(struct platform_device *pdev) { + const char *rxaeq_mode; struct device *dev = &pdev->dev; struct device_node *dn = dev->of_node, *child; const struct of_device_id *of_id; @@ -525,6 +590,13 @@ static int brcm_sata_phy_probe(struct platform_device *pdev) port->portnum = id; port->phy_priv = priv; port->phy = devm_phy_create(dev, child, &phy_ops); + port->rxaeq_mode = RXAEQ_MODE_OFF; + if (!of_property_read_string(child, "brcm,rxaeq-mode", + &rxaeq_mode)) + port->rxaeq_mode = rxaeq_to_val(rxaeq_mode); + if (port->rxaeq_mode == RXAEQ_MODE_MANUAL) + of_property_read_u32(child, "brcm,rxaeq-value", + &port->rxaeq_val); port->ssc_en = of_property_read_bool(child, "brcm,enable-ssc"); if (IS_ERR(port->phy)) { dev_err(dev, "failed to create PHY\n"); diff --git a/drivers/phy/broadcom/phy-brcm-usb-init.c b/drivers/phy/broadcom/phy-brcm-usb-init.c new file mode 100644 index 000000000000..1e7ce0b6f299 --- /dev/null +++ b/drivers/phy/broadcom/phy-brcm-usb-init.c @@ -0,0 +1,1017 @@ +/* + * phy-brcm-usb-init.c - Broadcom USB Phy chip specific init functions + * + * Copyright (C) 2014-2017 Broadcom + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * This module contains USB PHY initialization for power up and S3 resume + */ + +#include <linux/delay.h> +#include <linux/io.h> + +#include <linux/soc/brcmstb/brcmstb.h> +#include "phy-brcm-usb-init.h" + +#define PHY_PORTS 2 +#define PHY_PORT_SELECT_0 0 +#define PHY_PORT_SELECT_1 0x1000 + +/* Register definitions for the USB CTRL block */ +#define USB_CTRL_SETUP 0x00 +#define USB_CTRL_SETUP_IOC_MASK 0x00000010 +#define USB_CTRL_SETUP_IPP_MASK 0x00000020 +#define USB_CTRL_SETUP_BABO_MASK 0x00000001 +#define USB_CTRL_SETUP_FNHW_MASK 0x00000002 +#define USB_CTRL_SETUP_FNBO_MASK 0x00000004 +#define USB_CTRL_SETUP_WABO_MASK 0x00000008 +#define USB_CTRL_SETUP_SCB_CLIENT_SWAP_MASK 0x00002000 /* option */ +#define USB_CTRL_SETUP_SCB1_EN_MASK 0x00004000 /* option */ +#define USB_CTRL_SETUP_SCB2_EN_MASK 0x00008000 /* option */ +#define USB_CTRL_SETUP_SS_EHCI64BIT_EN_MASK 0X00020000 /* option */ +#define USB_CTRL_SETUP_SS_EHCI64BIT_EN_VAR_MASK 0x00010000 /* option */ +#define USB_CTRL_SETUP_STRAP_IPP_SEL_MASK 0x02000000 /* option */ +#define USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK 0x04000000 /* option */ +#define USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK 0x08000000 /* opt */ +#define USB_CTRL_SETUP_OC3_DISABLE_MASK 0xc0000000 /* option */ +#define USB_CTRL_PLL_CTL 0x04 +#define USB_CTRL_PLL_CTL_PLL_SUSPEND_EN_MASK 0x08000000 +#define USB_CTRL_PLL_CTL_PLL_RESETB_MASK 0x40000000 +#define USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK 0x80000000 /* option */ +#define USB_CTRL_EBRIDGE 0x0c +#define USB_CTRL_EBRIDGE_ESTOP_SCB_REQ_MASK 0x00020000 /* option */ +#define USB_CTRL_MDIO 0x14 +#define USB_CTRL_MDIO2 0x18 +#define USB_CTRL_UTMI_CTL_1 0x2c +#define USB_CTRL_UTMI_CTL_1_POWER_UP_FSM_EN_MASK 0x00000800 +#define USB_CTRL_UTMI_CTL_1_POWER_UP_FSM_EN_P1_MASK 0x08000000 +#define USB_CTRL_USB_PM 0x34 +#define USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK 0x00800000 /* option */ +#define USB_CTRL_USB_PM_XHC_SOFT_RESETB_MASK 0x00400000 /* option */ +#define USB_CTRL_USB_PM_XHC_SOFT_RESETB_VAR_MASK 0x40000000 /* option */ +#define USB_CTRL_USB_PM_USB_PWRDN_MASK 0x80000000 /* option */ +#define USB_CTRL_USB_PM_SOFT_RESET_MASK 0x40000000 /* option */ +#define USB_CTRL_USB_PM_USB20_HC_RESETB_MASK 0x30000000 /* option */ +#define USB_CTRL_USB_PM_USB20_HC_RESETB_VAR_MASK 0x00300000 /* option */ +#define USB_CTRL_USB30_CTL1 0x60 +#define USB_CTRL_USB30_CTL1_PHY3_PLL_SEQ_START_MASK 0x00000010 +#define USB_CTRL_USB30_CTL1_PHY3_RESETB_MASK 0x00010000 +#define USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK 0x00020000 /* option */ +#define USB_CTRL_USB30_CTL1_USB3_IOC_MASK 0x10000000 /* option */ +#define USB_CTRL_USB30_CTL1_USB3_IPP_MASK 0x20000000 /* option */ +#define USB_CTRL_USB30_PCTL 0x70 +#define USB_CTRL_USB30_PCTL_PHY3_SOFT_RESETB_MASK 0x00000002 +#define USB_CTRL_USB30_PCTL_PHY3_SOFT_RESETB_P1_MASK 0x00020000 +#define USB_CTRL_USB_DEVICE_CTL1 0x90 +#define USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK 0x00000003 /* option */ + +/* Register definitions for the XHCI EC block */ +#define USB_XHCI_EC_IRAADR 0x658 +#define USB_XHCI_EC_IRADAT 0x65c + +enum brcm_family_type { + BRCM_FAMILY_3390A0, + BRCM_FAMILY_7250B0, + BRCM_FAMILY_7271A0, + BRCM_FAMILY_7364A0, + BRCM_FAMILY_7366C0, + BRCM_FAMILY_74371A0, + BRCM_FAMILY_7439B0, + BRCM_FAMILY_7445D0, + BRCM_FAMILY_7260A0, + BRCM_FAMILY_7278A0, + BRCM_FAMILY_COUNT, +}; + +#define USB_BRCM_FAMILY(chip) \ + [BRCM_FAMILY_##chip] = __stringify(chip) + +static const char *family_names[BRCM_FAMILY_COUNT] = { + USB_BRCM_FAMILY(3390A0), + USB_BRCM_FAMILY(7250B0), + USB_BRCM_FAMILY(7271A0), + USB_BRCM_FAMILY(7364A0), + USB_BRCM_FAMILY(7366C0), + USB_BRCM_FAMILY(74371A0), + USB_BRCM_FAMILY(7439B0), + USB_BRCM_FAMILY(7445D0), + USB_BRCM_FAMILY(7260A0), + USB_BRCM_FAMILY(7278A0), +}; + +enum { + USB_CTRL_SETUP_SCB1_EN_SELECTOR, + USB_CTRL_SETUP_SCB2_EN_SELECTOR, + USB_CTRL_SETUP_SS_EHCI64BIT_EN_SELECTOR, + USB_CTRL_SETUP_STRAP_IPP_SEL_SELECTOR, + USB_CTRL_SETUP_OC3_DISABLE_SELECTOR, + USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_SELECTOR, + USB_CTRL_EBRIDGE_ESTOP_SCB_REQ_SELECTOR, + USB_CTRL_USB_PM_BDC_SOFT_RESETB_SELECTOR, + USB_CTRL_USB_PM_XHC_SOFT_RESETB_SELECTOR, + USB_CTRL_USB_PM_USB_PWRDN_SELECTOR, + USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_SELECTOR, + USB_CTRL_USB30_CTL1_USB3_IOC_SELECTOR, + USB_CTRL_USB30_CTL1_USB3_IPP_SELECTOR, + USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_SELECTOR, + USB_CTRL_USB_PM_SOFT_RESET_SELECTOR, + USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_SELECTOR, + USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_SELECTOR, + USB_CTRL_USB_PM_USB20_HC_RESETB_SELECTOR, + USB_CTRL_SETUP_ENDIAN_SELECTOR, + USB_CTRL_SELECTOR_COUNT, +}; + +#define USB_CTRL_REG(base, reg) ((void *)base + USB_CTRL_##reg) +#define USB_XHCI_EC_REG(base, reg) ((void *)base + USB_XHCI_EC_##reg) +#define USB_CTRL_MASK(reg, field) \ + USB_CTRL_##reg##_##field##_MASK +#define USB_CTRL_MASK_FAMILY(params, reg, field) \ + (params->usb_reg_bits_map[USB_CTRL_##reg##_##field##_SELECTOR]) + +#define USB_CTRL_SET_FAMILY(params, reg, field) \ + usb_ctrl_set_family(params, USB_CTRL_##reg, \ + USB_CTRL_##reg##_##field##_SELECTOR) +#define USB_CTRL_UNSET_FAMILY(params, reg, field) \ + usb_ctrl_unset_family(params, USB_CTRL_##reg, \ + USB_CTRL_##reg##_##field##_SELECTOR) + +#define USB_CTRL_SET(base, reg, field) \ + usb_ctrl_set(USB_CTRL_REG(base, reg), \ + USB_CTRL_##reg##_##field##_MASK) +#define USB_CTRL_UNSET(base, reg, field) \ + usb_ctrl_unset(USB_CTRL_REG(base, reg), \ + USB_CTRL_##reg##_##field##_MASK) + +#define MDIO_USB2 0 +#define MDIO_USB3 BIT(31) + +#define USB_CTRL_SETUP_ENDIAN_BITS ( \ + USB_CTRL_MASK(SETUP, BABO) | \ + USB_CTRL_MASK(SETUP, FNHW) | \ + USB_CTRL_MASK(SETUP, FNBO) | \ + USB_CTRL_MASK(SETUP, WABO)) + +#ifdef __LITTLE_ENDIAN +#define ENDIAN_SETTINGS ( \ + USB_CTRL_MASK(SETUP, BABO) | \ + USB_CTRL_MASK(SETUP, FNHW)) +#else +#define ENDIAN_SETTINGS ( \ + USB_CTRL_MASK(SETUP, FNHW) | \ + USB_CTRL_MASK(SETUP, FNBO) | \ + USB_CTRL_MASK(SETUP, WABO)) +#endif + +struct id_to_type { + u32 id; + int type; +}; + +static const struct id_to_type id_to_type_table[] = { + { 0x33900000, BRCM_FAMILY_3390A0 }, + { 0x72500010, BRCM_FAMILY_7250B0 }, + { 0x72600000, BRCM_FAMILY_7260A0 }, + { 0x72680000, BRCM_FAMILY_7271A0 }, + { 0x72710000, BRCM_FAMILY_7271A0 }, + { 0x73640000, BRCM_FAMILY_7364A0 }, + { 0x73660020, BRCM_FAMILY_7366C0 }, + { 0x07437100, BRCM_FAMILY_74371A0 }, + { 0x74390010, BRCM_FAMILY_7439B0 }, + { 0x74450030, BRCM_FAMILY_7445D0 }, + { 0x72780000, BRCM_FAMILY_7278A0 }, + { 0, BRCM_FAMILY_7271A0 }, /* default */ +}; + +static const u32 +usb_reg_bits_map_table[BRCM_FAMILY_COUNT][USB_CTRL_SELECTOR_COUNT] = { + /* 3390B0 */ + [BRCM_FAMILY_3390A0] = { + USB_CTRL_SETUP_SCB1_EN_MASK, + USB_CTRL_SETUP_SCB2_EN_MASK, + USB_CTRL_SETUP_SS_EHCI64BIT_EN_MASK, + USB_CTRL_SETUP_STRAP_IPP_SEL_MASK, + USB_CTRL_SETUP_OC3_DISABLE_MASK, + 0, /* USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK */ + USB_CTRL_EBRIDGE_ESTOP_SCB_REQ_MASK, + 0, /* USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK */ + USB_CTRL_USB_PM_XHC_SOFT_RESETB_MASK, + USB_CTRL_USB_PM_USB_PWRDN_MASK, + 0, /* USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK */ + 0, /* USB_CTRL_USB30_CTL1_USB3_IOC_MASK */ + 0, /* USB_CTRL_USB30_CTL1_USB3_IPP_MASK */ + USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK, + 0, /* USB_CTRL_USB_PM_SOFT_RESET_MASK */ + 0, /* USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK */ + 0, /* USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK */ + USB_CTRL_USB_PM_USB20_HC_RESETB_VAR_MASK, + ENDIAN_SETTINGS, /* USB_CTRL_SETUP ENDIAN bits */ + }, + /* 7250b0 */ + [BRCM_FAMILY_7250B0] = { + USB_CTRL_SETUP_SCB1_EN_MASK, + USB_CTRL_SETUP_SCB2_EN_MASK, + USB_CTRL_SETUP_SS_EHCI64BIT_EN_MASK, + 0, /* USB_CTRL_SETUP_STRAP_IPP_SEL_MASK */ + USB_CTRL_SETUP_OC3_DISABLE_MASK, + USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK, + USB_CTRL_EBRIDGE_ESTOP_SCB_REQ_MASK, + 0, /* USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK */ + USB_CTRL_USB_PM_XHC_SOFT_RESETB_VAR_MASK, + 0, /* USB_CTRL_USB_PM_USB_PWRDN_MASK */ + 0, /* USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK */ + 0, /* USB_CTRL_USB30_CTL1_USB3_IOC_MASK */ + 0, /* USB_CTRL_USB30_CTL1_USB3_IPP_MASK */ + 0, /* USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK */ + 0, /* USB_CTRL_USB_PM_SOFT_RESET_MASK */ + 0, /* USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK */ + 0, /* USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK */ + USB_CTRL_USB_PM_USB20_HC_RESETB_MASK, + ENDIAN_SETTINGS, /* USB_CTRL_SETUP ENDIAN bits */ + }, + /* 7271a0 */ + [BRCM_FAMILY_7271A0] = { + 0, /* USB_CTRL_SETUP_SCB1_EN_MASK */ + 0, /* USB_CTRL_SETUP_SCB2_EN_MASK */ + USB_CTRL_SETUP_SS_EHCI64BIT_EN_MASK, + USB_CTRL_SETUP_STRAP_IPP_SEL_MASK, + USB_CTRL_SETUP_OC3_DISABLE_MASK, + 0, /* USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK */ + USB_CTRL_EBRIDGE_ESTOP_SCB_REQ_MASK, + USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK, + USB_CTRL_USB_PM_XHC_SOFT_RESETB_MASK, + USB_CTRL_USB_PM_USB_PWRDN_MASK, + 0, /* USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK */ + 0, /* USB_CTRL_USB30_CTL1_USB3_IOC_MASK */ + 0, /* USB_CTRL_USB30_CTL1_USB3_IPP_MASK */ + USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK, + USB_CTRL_USB_PM_SOFT_RESET_MASK, + USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK, + USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK, + USB_CTRL_USB_PM_USB20_HC_RESETB_VAR_MASK, + ENDIAN_SETTINGS, /* USB_CTRL_SETUP ENDIAN bits */ + }, + /* 7364a0 */ + [BRCM_FAMILY_7364A0] = { + USB_CTRL_SETUP_SCB1_EN_MASK, + USB_CTRL_SETUP_SCB2_EN_MASK, + USB_CTRL_SETUP_SS_EHCI64BIT_EN_MASK, + 0, /* USB_CTRL_SETUP_STRAP_IPP_SEL_MASK */ + USB_CTRL_SETUP_OC3_DISABLE_MASK, + USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK, + USB_CTRL_EBRIDGE_ESTOP_SCB_REQ_MASK, + 0, /* USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK */ + USB_CTRL_USB_PM_XHC_SOFT_RESETB_VAR_MASK, + 0, /* USB_CTRL_USB_PM_USB_PWRDN_MASK */ + 0, /* USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK */ + 0, /* USB_CTRL_USB30_CTL1_USB3_IOC_MASK */ + 0, /* USB_CTRL_USB30_CTL1_USB3_IPP_MASK */ + 0, /* USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK */ + 0, /* USB_CTRL_USB_PM_SOFT_RESET_MASK */ + 0, /* USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK */ + 0, /* USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK */ + USB_CTRL_USB_PM_USB20_HC_RESETB_MASK, + ENDIAN_SETTINGS, /* USB_CTRL_SETUP ENDIAN bits */ + }, + /* 7366c0 */ + [BRCM_FAMILY_7366C0] = { + USB_CTRL_SETUP_SCB1_EN_MASK, + USB_CTRL_SETUP_SCB2_EN_MASK, + USB_CTRL_SETUP_SS_EHCI64BIT_EN_MASK, + 0, /* USB_CTRL_SETUP_STRAP_IPP_SEL_MASK */ + USB_CTRL_SETUP_OC3_DISABLE_MASK, + 0, /* USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK */ + USB_CTRL_EBRIDGE_ESTOP_SCB_REQ_MASK, + 0, /* USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK */ + USB_CTRL_USB_PM_XHC_SOFT_RESETB_VAR_MASK, + USB_CTRL_USB_PM_USB_PWRDN_MASK, + 0, /* USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK */ + 0, /* USB_CTRL_USB30_CTL1_USB3_IOC_MASK */ + 0, /* USB_CTRL_USB30_CTL1_USB3_IPP_MASK */ + 0, /* USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK */ + 0, /* USB_CTRL_USB_PM_SOFT_RESET_MASK */ + 0, /* USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK */ + 0, /* USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK */ + USB_CTRL_USB_PM_USB20_HC_RESETB_MASK, + ENDIAN_SETTINGS, /* USB_CTRL_SETUP ENDIAN bits */ + }, + /* 74371A0 */ + [BRCM_FAMILY_74371A0] = { + USB_CTRL_SETUP_SCB1_EN_MASK, + USB_CTRL_SETUP_SCB2_EN_MASK, + USB_CTRL_SETUP_SS_EHCI64BIT_EN_VAR_MASK, + 0, /* USB_CTRL_SETUP_STRAP_IPP_SEL_MASK */ + 0, /* USB_CTRL_SETUP_OC3_DISABLE_MASK */ + USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK, + 0, /* USB_CTRL_EBRIDGE_ESTOP_SCB_REQ_MASK */ + 0, /* USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK */ + 0, /* USB_CTRL_USB_PM_XHC_SOFT_RESETB_MASK */ + 0, /* USB_CTRL_USB_PM_USB_PWRDN_MASK */ + USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK, + USB_CTRL_USB30_CTL1_USB3_IOC_MASK, + USB_CTRL_USB30_CTL1_USB3_IPP_MASK, + 0, /* USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK */ + 0, /* USB_CTRL_USB_PM_SOFT_RESET_MASK */ + 0, /* USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK */ + 0, /* USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK */ + 0, /* USB_CTRL_USB_PM_USB20_HC_RESETB_MASK */ + ENDIAN_SETTINGS, /* USB_CTRL_SETUP ENDIAN bits */ + }, + /* 7439B0 */ + [BRCM_FAMILY_7439B0] = { + USB_CTRL_SETUP_SCB1_EN_MASK, + USB_CTRL_SETUP_SCB2_EN_MASK, + USB_CTRL_SETUP_SS_EHCI64BIT_EN_MASK, + USB_CTRL_SETUP_STRAP_IPP_SEL_MASK, + USB_CTRL_SETUP_OC3_DISABLE_MASK, + 0, /* USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK */ + 0, /* USB_CTRL_EBRIDGE_ESTOP_SCB_REQ_MASK */ + USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK, + USB_CTRL_USB_PM_XHC_SOFT_RESETB_MASK, + USB_CTRL_USB_PM_USB_PWRDN_MASK, + 0, /* USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK */ + 0, /* USB_CTRL_USB30_CTL1_USB3_IOC_MASK */ + 0, /* USB_CTRL_USB30_CTL1_USB3_IPP_MASK */ + USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK, + 0, /* USB_CTRL_USB_PM_SOFT_RESET_MASK */ + 0, /* USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK */ + 0, /* USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK */ + USB_CTRL_USB_PM_USB20_HC_RESETB_VAR_MASK, + ENDIAN_SETTINGS, /* USB_CTRL_SETUP ENDIAN bits */ + }, + /* 7445d0 */ + [BRCM_FAMILY_7445D0] = { + USB_CTRL_SETUP_SCB1_EN_MASK, + USB_CTRL_SETUP_SCB2_EN_MASK, + USB_CTRL_SETUP_SS_EHCI64BIT_EN_VAR_MASK, + 0, /* USB_CTRL_SETUP_STRAP_IPP_SEL_MASK */ + USB_CTRL_SETUP_OC3_DISABLE_MASK, + USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK, + 0, /* USB_CTRL_EBRIDGE_ESTOP_SCB_REQ_MASK */ + 0, /* USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK */ + 0, /* USB_CTRL_USB_PM_XHC_SOFT_RESETB_MASK */ + 0, /* USB_CTRL_USB_PM_USB_PWRDN_MASK */ + USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK, + 0, /* USB_CTRL_USB30_CTL1_USB3_IOC_MASK */ + 0, /* USB_CTRL_USB30_CTL1_USB3_IPP_MASK */ + 0, /* USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK */ + 0, /* USB_CTRL_USB_PM_SOFT_RESET_MASK */ + 0, /* USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK */ + 0, /* USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK */ + USB_CTRL_USB_PM_USB20_HC_RESETB_VAR_MASK, + ENDIAN_SETTINGS, /* USB_CTRL_SETUP ENDIAN bits */ + }, + /* 7260a0 */ + [BRCM_FAMILY_7260A0] = { + 0, /* USB_CTRL_SETUP_SCB1_EN_MASK */ + 0, /* USB_CTRL_SETUP_SCB2_EN_MASK */ + USB_CTRL_SETUP_SS_EHCI64BIT_EN_MASK, + USB_CTRL_SETUP_STRAP_IPP_SEL_MASK, + USB_CTRL_SETUP_OC3_DISABLE_MASK, + 0, /* USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK */ + USB_CTRL_EBRIDGE_ESTOP_SCB_REQ_MASK, + USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK, + USB_CTRL_USB_PM_XHC_SOFT_RESETB_MASK, + USB_CTRL_USB_PM_USB_PWRDN_MASK, + 0, /* USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK */ + 0, /* USB_CTRL_USB30_CTL1_USB3_IOC_MASK */ + 0, /* USB_CTRL_USB30_CTL1_USB3_IPP_MASK */ + USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK, + USB_CTRL_USB_PM_SOFT_RESET_MASK, + USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK, + USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK, + USB_CTRL_USB_PM_USB20_HC_RESETB_VAR_MASK, + ENDIAN_SETTINGS, /* USB_CTRL_SETUP ENDIAN bits */ + }, + /* 7278a0 */ + [BRCM_FAMILY_7278A0] = { + 0, /* USB_CTRL_SETUP_SCB1_EN_MASK */ + 0, /* USB_CTRL_SETUP_SCB2_EN_MASK */ + 0, /*USB_CTRL_SETUP_SS_EHCI64BIT_EN_MASK */ + USB_CTRL_SETUP_STRAP_IPP_SEL_MASK, + USB_CTRL_SETUP_OC3_DISABLE_MASK, + 0, /* USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK */ + USB_CTRL_EBRIDGE_ESTOP_SCB_REQ_MASK, + USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK, + USB_CTRL_USB_PM_XHC_SOFT_RESETB_MASK, + USB_CTRL_USB_PM_USB_PWRDN_MASK, + 0, /* USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK */ + 0, /* USB_CTRL_USB30_CTL1_USB3_IOC_MASK */ + 0, /* USB_CTRL_USB30_CTL1_USB3_IPP_MASK */ + USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK, + USB_CTRL_USB_PM_SOFT_RESET_MASK, + 0, /* USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK */ + 0, /* USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK */ + 0, /* USB_CTRL_USB_PM_USB20_HC_RESETB_MASK */ + 0, /* USB_CTRL_SETUP ENDIAN bits */ + }, +}; + +static inline u32 brcmusb_readl(void __iomem *addr) +{ + return readl(addr); +} + +static inline void brcmusb_writel(u32 val, void __iomem *addr) +{ + writel(val, addr); +} + +static inline +void usb_ctrl_unset_family(struct brcm_usb_init_params *params, + u32 reg_offset, u32 field) +{ + u32 mask; + void *reg; + + mask = params->usb_reg_bits_map[field]; + reg = params->ctrl_regs + reg_offset; + brcmusb_writel(brcmusb_readl(reg) & ~mask, reg); +}; + +static inline +void usb_ctrl_set_family(struct brcm_usb_init_params *params, + u32 reg_offset, u32 field) +{ + u32 mask; + void *reg; + + mask = params->usb_reg_bits_map[field]; + reg = params->ctrl_regs + reg_offset; + brcmusb_writel(brcmusb_readl(reg) | mask, reg); +}; + +static inline void usb_ctrl_set(void __iomem *reg, u32 field) +{ + u32 value; + + value = brcmusb_readl(reg); + brcmusb_writel(value | field, reg); +} + +static inline void usb_ctrl_unset(void __iomem *reg, u32 field) +{ + u32 value; + + value = brcmusb_readl(reg); + brcmusb_writel(value & ~field, reg); +} + +static u32 brcmusb_usb_mdio_read(void __iomem *ctrl_base, u32 reg, int mode) +{ + u32 data; + + data = (reg << 16) | mode; + brcmusb_writel(data, USB_CTRL_REG(ctrl_base, MDIO)); + data |= (1 << 24); + brcmusb_writel(data, USB_CTRL_REG(ctrl_base, MDIO)); + data &= ~(1 << 24); + /* wait for the 60MHz parallel to serial shifter */ + usleep_range(10, 20); + brcmusb_writel(data, USB_CTRL_REG(ctrl_base, MDIO)); + /* wait for the 60MHz parallel to serial shifter */ + usleep_range(10, 20); + + return brcmusb_readl(USB_CTRL_REG(ctrl_base, MDIO2)) & 0xffff; +} + +static void brcmusb_usb_mdio_write(void __iomem *ctrl_base, u32 reg, + u32 val, int mode) +{ + u32 data; + + data = (reg << 16) | val | mode; + brcmusb_writel(data, USB_CTRL_REG(ctrl_base, MDIO)); + data |= (1 << 25); + brcmusb_writel(data, USB_CTRL_REG(ctrl_base, MDIO)); + data &= ~(1 << 25); + + /* wait for the 60MHz parallel to serial shifter */ + usleep_range(10, 20); + brcmusb_writel(data, USB_CTRL_REG(ctrl_base, MDIO)); + /* wait for the 60MHz parallel to serial shifter */ + usleep_range(10, 20); +} + +static void brcmusb_usb_phy_ldo_fix(void __iomem *ctrl_base) +{ + /* first disable FSM but also leave it that way */ + /* to allow normal suspend/resume */ + USB_CTRL_UNSET(ctrl_base, UTMI_CTL_1, POWER_UP_FSM_EN); + USB_CTRL_UNSET(ctrl_base, UTMI_CTL_1, POWER_UP_FSM_EN_P1); + + /* reset USB 2.0 PLL */ + USB_CTRL_UNSET(ctrl_base, PLL_CTL, PLL_RESETB); + /* PLL reset period */ + udelay(1); + USB_CTRL_SET(ctrl_base, PLL_CTL, PLL_RESETB); + /* Give PLL enough time to lock */ + usleep_range(1000, 2000); +} + +static void brcmusb_usb2_eye_fix(void __iomem *ctrl_base) +{ + /* Increase USB 2.0 TX level to meet spec requirement */ + brcmusb_usb_mdio_write(ctrl_base, 0x1f, 0x80a0, MDIO_USB2); + brcmusb_usb_mdio_write(ctrl_base, 0x0a, 0xc6a0, MDIO_USB2); +} + +static void brcmusb_usb3_pll_fix(void __iomem *ctrl_base) +{ + /* Set correct window for PLL lock detect */ + brcmusb_usb_mdio_write(ctrl_base, 0x1f, 0x8000, MDIO_USB3); + brcmusb_usb_mdio_write(ctrl_base, 0x07, 0x1503, MDIO_USB3); +} + +static void brcmusb_usb3_enable_pipe_reset(void __iomem *ctrl_base) +{ + u32 val; + + /* Re-enable USB 3.0 pipe reset */ + brcmusb_usb_mdio_write(ctrl_base, 0x1f, 0x8000, MDIO_USB3); + val = brcmusb_usb_mdio_read(ctrl_base, 0x0f, MDIO_USB3) | 0x200; + brcmusb_usb_mdio_write(ctrl_base, 0x0f, val, MDIO_USB3); +} + +static void brcmusb_usb3_enable_sigdet(void __iomem *ctrl_base) +{ + u32 val, ofs; + int ii; + + ofs = 0; + for (ii = 0; ii < PHY_PORTS; ++ii) { + /* Set correct default for sigdet */ + brcmusb_usb_mdio_write(ctrl_base, 0x1f, (0x8080 + ofs), + MDIO_USB3); + val = brcmusb_usb_mdio_read(ctrl_base, 0x05, MDIO_USB3); + val = (val & ~0x800f) | 0x800d; + brcmusb_usb_mdio_write(ctrl_base, 0x05, val, MDIO_USB3); + ofs = PHY_PORT_SELECT_1; + } +} + +static void brcmusb_usb3_enable_skip_align(void __iomem *ctrl_base) +{ + u32 val, ofs; + int ii; + + ofs = 0; + for (ii = 0; ii < PHY_PORTS; ++ii) { + /* Set correct default for SKIP align */ + brcmusb_usb_mdio_write(ctrl_base, 0x1f, (0x8060 + ofs), + MDIO_USB3); + val = brcmusb_usb_mdio_read(ctrl_base, 0x01, MDIO_USB3) | 0x200; + brcmusb_usb_mdio_write(ctrl_base, 0x01, val, MDIO_USB3); + ofs = PHY_PORT_SELECT_1; + } +} + +static void brcmusb_usb3_unfreeze_aeq(void __iomem *ctrl_base) +{ + u32 val, ofs; + int ii; + + ofs = 0; + for (ii = 0; ii < PHY_PORTS; ++ii) { + /* Let EQ freeze after TSEQ */ + brcmusb_usb_mdio_write(ctrl_base, 0x1f, (0x80e0 + ofs), + MDIO_USB3); + val = brcmusb_usb_mdio_read(ctrl_base, 0x01, MDIO_USB3); + val &= ~0x0008; + brcmusb_usb_mdio_write(ctrl_base, 0x01, val, MDIO_USB3); + ofs = PHY_PORT_SELECT_1; + } +} + +static void brcmusb_usb3_pll_54mhz(struct brcm_usb_init_params *params) +{ + u32 ofs; + int ii; + void __iomem *ctrl_base = params->ctrl_regs; + + /* + * On newer B53 based SoC's, the reference clock for the + * 3.0 PLL has been changed from 50MHz to 54MHz so the + * PLL needs to be reprogrammed. + * See SWLINUX-4006. + * + * On the 7364C0, the reference clock for the + * 3.0 PLL has been changed from 50MHz to 54MHz to + * work around a MOCA issue. + * See SWLINUX-4169. + */ + switch (params->selected_family) { + case BRCM_FAMILY_3390A0: + case BRCM_FAMILY_7250B0: + case BRCM_FAMILY_7366C0: + case BRCM_FAMILY_74371A0: + case BRCM_FAMILY_7439B0: + case BRCM_FAMILY_7445D0: + case BRCM_FAMILY_7260A0: + return; + case BRCM_FAMILY_7364A0: + if (BRCM_REV(params->family_id) < 0x20) + return; + break; + } + + /* set USB 3.0 PLL to accept 54Mhz reference clock */ + USB_CTRL_UNSET(ctrl_base, USB30_CTL1, PHY3_PLL_SEQ_START); + + brcmusb_usb_mdio_write(ctrl_base, 0x1f, 0x8000, MDIO_USB3); + brcmusb_usb_mdio_write(ctrl_base, 0x10, 0x5784, MDIO_USB3); + brcmusb_usb_mdio_write(ctrl_base, 0x11, 0x01d0, MDIO_USB3); + brcmusb_usb_mdio_write(ctrl_base, 0x12, 0x1DE8, MDIO_USB3); + brcmusb_usb_mdio_write(ctrl_base, 0x13, 0xAA80, MDIO_USB3); + brcmusb_usb_mdio_write(ctrl_base, 0x14, 0x8826, MDIO_USB3); + brcmusb_usb_mdio_write(ctrl_base, 0x15, 0x0044, MDIO_USB3); + brcmusb_usb_mdio_write(ctrl_base, 0x16, 0x8000, MDIO_USB3); + brcmusb_usb_mdio_write(ctrl_base, 0x17, 0x0851, MDIO_USB3); + brcmusb_usb_mdio_write(ctrl_base, 0x18, 0x0000, MDIO_USB3); + + /* both ports */ + ofs = 0; + for (ii = 0; ii < PHY_PORTS; ++ii) { + brcmusb_usb_mdio_write(ctrl_base, 0x1f, (0x8040 + ofs), + MDIO_USB3); + brcmusb_usb_mdio_write(ctrl_base, 0x03, 0x0090, MDIO_USB3); + brcmusb_usb_mdio_write(ctrl_base, 0x04, 0x0134, MDIO_USB3); + brcmusb_usb_mdio_write(ctrl_base, 0x1f, (0x8020 + ofs), + MDIO_USB3); + brcmusb_usb_mdio_write(ctrl_base, 0x01, 0x00e2, MDIO_USB3); + ofs = PHY_PORT_SELECT_1; + } + + /* restart PLL sequence */ + USB_CTRL_SET(ctrl_base, USB30_CTL1, PHY3_PLL_SEQ_START); + /* Give PLL enough time to lock */ + usleep_range(1000, 2000); +} + +static void brcmusb_usb3_ssc_enable(void __iomem *ctrl_base) +{ + u32 val; + + /* Enable USB 3.0 TX spread spectrum */ + brcmusb_usb_mdio_write(ctrl_base, 0x1f, 0x8040, MDIO_USB3); + val = brcmusb_usb_mdio_read(ctrl_base, 0x01, MDIO_USB3) | 0xf; + brcmusb_usb_mdio_write(ctrl_base, 0x01, val, MDIO_USB3); + + /* Currently, USB 3.0 SSC is enabled via port 0 MDIO registers, + * which should have been adequate. However, due to a bug in the + * USB 3.0 PHY, it must be enabled via both ports (HWUSB3DVT-26). + */ + brcmusb_usb_mdio_write(ctrl_base, 0x1f, 0x9040, MDIO_USB3); + val = brcmusb_usb_mdio_read(ctrl_base, 0x01, MDIO_USB3) | 0xf; + brcmusb_usb_mdio_write(ctrl_base, 0x01, val, MDIO_USB3); +} + +static void brcmusb_usb3_phy_workarounds(struct brcm_usb_init_params *params) +{ + void __iomem *ctrl_base = params->ctrl_regs; + + brcmusb_usb3_pll_fix(ctrl_base); + brcmusb_usb3_pll_54mhz(params); + brcmusb_usb3_ssc_enable(ctrl_base); + brcmusb_usb3_enable_pipe_reset(ctrl_base); + brcmusb_usb3_enable_sigdet(ctrl_base); + brcmusb_usb3_enable_skip_align(ctrl_base); + brcmusb_usb3_unfreeze_aeq(ctrl_base); +} + +static void brcmusb_memc_fix(struct brcm_usb_init_params *params) +{ + u32 prid; + + if (params->selected_family != BRCM_FAMILY_7445D0) + return; + /* + * This is a workaround for HW7445-1869 where a DMA write ends up + * doing a read pre-fetch after the end of the DMA buffer. This + * causes a problem when the DMA buffer is at the end of physical + * memory, causing the pre-fetch read to access non-existent memory, + * and the chip bondout has MEMC2 disabled. When the pre-fetch read + * tries to use the disabled MEMC2, it hangs the bus. The workaround + * is to disable MEMC2 access in the usb controller which avoids + * the hang. + */ + + prid = params->product_id & 0xfffff000; + switch (prid) { + case 0x72520000: + case 0x74480000: + case 0x74490000: + case 0x07252000: + case 0x07448000: + case 0x07449000: + USB_CTRL_UNSET_FAMILY(params, SETUP, SCB2_EN); + } +} + +static void brcmusb_usb3_otp_fix(struct brcm_usb_init_params *params) +{ + void __iomem *xhci_ec_base = params->xhci_ec_regs; + u32 val; + + if (params->family_id != 0x74371000 || xhci_ec_base == 0) + return; + brcmusb_writel(0xa20c, USB_XHCI_EC_REG(xhci_ec_base, IRAADR)); + val = brcmusb_readl(USB_XHCI_EC_REG(xhci_ec_base, IRADAT)); + + /* set cfg_pick_ss_lock */ + val |= (1 << 27); + brcmusb_writel(val, USB_XHCI_EC_REG(xhci_ec_base, IRADAT)); + + /* Reset USB 3.0 PHY for workaround to take effect */ + USB_CTRL_UNSET(params->ctrl_regs, USB30_CTL1, PHY3_RESETB); + USB_CTRL_SET(params->ctrl_regs, USB30_CTL1, PHY3_RESETB); +} + +static void brcmusb_xhci_soft_reset(struct brcm_usb_init_params *params, + int on_off) +{ + /* Assert reset */ + if (on_off) { + if (USB_CTRL_MASK_FAMILY(params, USB_PM, XHC_SOFT_RESETB)) + USB_CTRL_UNSET_FAMILY(params, USB_PM, XHC_SOFT_RESETB); + else + USB_CTRL_UNSET_FAMILY(params, + USB30_CTL1, XHC_SOFT_RESETB); + } else { /* De-assert reset */ + if (USB_CTRL_MASK_FAMILY(params, USB_PM, XHC_SOFT_RESETB)) + USB_CTRL_SET_FAMILY(params, USB_PM, XHC_SOFT_RESETB); + else + USB_CTRL_SET_FAMILY(params, USB30_CTL1, + XHC_SOFT_RESETB); + } +} + +/* + * Return the best map table family. The order is: + * - exact match of chip and major rev + * - exact match of chip and closest older major rev + * - default chip/rev. + * NOTE: The minor rev is always ignored. + */ +static enum brcm_family_type brcmusb_get_family_type( + struct brcm_usb_init_params *params) +{ + int last_type = -1; + u32 last_family = 0; + u32 family_no_major; + unsigned int x; + u32 family; + + family = params->family_id & 0xfffffff0; + family_no_major = params->family_id & 0xffffff00; + for (x = 0; id_to_type_table[x].id; x++) { + if (family == id_to_type_table[x].id) + return id_to_type_table[x].type; + if (family_no_major == (id_to_type_table[x].id & 0xffffff00)) + if (family > id_to_type_table[x].id && + last_family < id_to_type_table[x].id) { + last_family = id_to_type_table[x].id; + last_type = id_to_type_table[x].type; + } + } + + /* If no match, return the default family */ + if (last_type == -1) + return id_to_type_table[x].type; + return last_type; +} + +void brcm_usb_init_ipp(struct brcm_usb_init_params *params) +{ + void __iomem *ctrl = params->ctrl_regs; + u32 reg; + u32 orig_reg; + + /* Starting with the 7445d0, there are no longer separate 3.0 + * versions of IOC and IPP. + */ + if (USB_CTRL_MASK_FAMILY(params, USB30_CTL1, USB3_IOC)) { + if (params->ioc) + USB_CTRL_SET_FAMILY(params, USB30_CTL1, USB3_IOC); + if (params->ipp == 1) + USB_CTRL_SET_FAMILY(params, USB30_CTL1, USB3_IPP); + } + + reg = brcmusb_readl(USB_CTRL_REG(ctrl, SETUP)); + orig_reg = reg; + if (USB_CTRL_MASK_FAMILY(params, SETUP, STRAP_CC_DRD_MODE_ENABLE_SEL)) + /* Never use the strap, it's going away. */ + reg &= ~(USB_CTRL_MASK_FAMILY(params, + SETUP, + STRAP_CC_DRD_MODE_ENABLE_SEL)); + if (USB_CTRL_MASK_FAMILY(params, SETUP, STRAP_IPP_SEL)) + if (params->ipp != 2) + /* override ipp strap pin (if it exits) */ + reg &= ~(USB_CTRL_MASK_FAMILY(params, SETUP, + STRAP_IPP_SEL)); + + /* Override the default OC and PP polarity */ + reg &= ~(USB_CTRL_MASK(SETUP, IPP) | USB_CTRL_MASK(SETUP, IOC)); + if (params->ioc) + reg |= USB_CTRL_MASK(SETUP, IOC); + if (params->ipp == 1 && ((reg & USB_CTRL_MASK(SETUP, IPP)) == 0)) + reg |= USB_CTRL_MASK(SETUP, IPP); + brcmusb_writel(reg, USB_CTRL_REG(ctrl, SETUP)); + + /* + * If we're changing IPP, make sure power is off long enough + * to turn off any connected devices. + */ + if (reg != orig_reg) + msleep(50); +} + +int brcm_usb_init_get_dual_select(struct brcm_usb_init_params *params) +{ + void __iomem *ctrl = params->ctrl_regs; + u32 reg = 0; + + if (USB_CTRL_MASK_FAMILY(params, USB_DEVICE_CTL1, PORT_MODE)) { + reg = brcmusb_readl(USB_CTRL_REG(ctrl, USB_DEVICE_CTL1)); + reg &= USB_CTRL_MASK_FAMILY(params, USB_DEVICE_CTL1, + PORT_MODE); + } + return reg; +} + +void brcm_usb_init_set_dual_select(struct brcm_usb_init_params *params, + int mode) +{ + void __iomem *ctrl = params->ctrl_regs; + u32 reg; + + if (USB_CTRL_MASK_FAMILY(params, USB_DEVICE_CTL1, PORT_MODE)) { + reg = brcmusb_readl(USB_CTRL_REG(ctrl, USB_DEVICE_CTL1)); + reg &= ~USB_CTRL_MASK_FAMILY(params, USB_DEVICE_CTL1, + PORT_MODE); + reg |= mode; + brcmusb_writel(reg, USB_CTRL_REG(ctrl, USB_DEVICE_CTL1)); + } +} + +void brcm_usb_init_common(struct brcm_usb_init_params *params) +{ + u32 reg; + void __iomem *ctrl = params->ctrl_regs; + + /* Take USB out of power down */ + if (USB_CTRL_MASK_FAMILY(params, PLL_CTL, PLL_IDDQ_PWRDN)) { + USB_CTRL_UNSET_FAMILY(params, PLL_CTL, PLL_IDDQ_PWRDN); + /* 1 millisecond - for USB clocks to settle down */ + usleep_range(1000, 2000); + } + + if (USB_CTRL_MASK_FAMILY(params, USB_PM, USB_PWRDN)) { + USB_CTRL_UNSET_FAMILY(params, USB_PM, USB_PWRDN); + /* 1 millisecond - for USB clocks to settle down */ + usleep_range(1000, 2000); + } + + if (params->selected_family != BRCM_FAMILY_74371A0 && + (BRCM_ID(params->family_id) != 0x7364)) + /* + * HW7439-637: 7439a0 and its derivatives do not have large + * enough descriptor storage for this. + */ + USB_CTRL_SET_FAMILY(params, SETUP, SS_EHCI64BIT_EN); + + /* Block auto PLL suspend by USB2 PHY (Sasi) */ + USB_CTRL_SET(ctrl, PLL_CTL, PLL_SUSPEND_EN); + + reg = brcmusb_readl(USB_CTRL_REG(ctrl, SETUP)); + if (params->selected_family == BRCM_FAMILY_7364A0) + /* Suppress overcurrent indication from USB30 ports for A0 */ + reg |= USB_CTRL_MASK_FAMILY(params, SETUP, OC3_DISABLE); + + brcmusb_usb_phy_ldo_fix(ctrl); + brcmusb_usb2_eye_fix(ctrl); + + /* + * Make sure the the second and third memory controller + * interfaces are enabled if they exist. + */ + if (USB_CTRL_MASK_FAMILY(params, SETUP, SCB1_EN)) + reg |= USB_CTRL_MASK_FAMILY(params, SETUP, SCB1_EN); + if (USB_CTRL_MASK_FAMILY(params, SETUP, SCB2_EN)) + reg |= USB_CTRL_MASK_FAMILY(params, SETUP, SCB2_EN); + brcmusb_writel(reg, USB_CTRL_REG(ctrl, SETUP)); + + brcmusb_memc_fix(params); + + if (USB_CTRL_MASK_FAMILY(params, USB_DEVICE_CTL1, PORT_MODE)) { + reg = brcmusb_readl(USB_CTRL_REG(ctrl, USB_DEVICE_CTL1)); + reg &= ~USB_CTRL_MASK_FAMILY(params, USB_DEVICE_CTL1, + PORT_MODE); + reg |= params->mode; + brcmusb_writel(reg, USB_CTRL_REG(ctrl, USB_DEVICE_CTL1)); + } + if (USB_CTRL_MASK_FAMILY(params, USB_PM, BDC_SOFT_RESETB)) { + switch (params->mode) { + case USB_CTLR_MODE_HOST: + USB_CTRL_UNSET_FAMILY(params, USB_PM, BDC_SOFT_RESETB); + break; + default: + USB_CTRL_SET_FAMILY(params, USB_PM, BDC_SOFT_RESETB); + break; + } + } + if (USB_CTRL_MASK_FAMILY(params, SETUP, CC_DRD_MODE_ENABLE)) { + if (params->mode == USB_CTLR_MODE_TYPEC_PD) + USB_CTRL_SET_FAMILY(params, SETUP, CC_DRD_MODE_ENABLE); + else + USB_CTRL_UNSET_FAMILY(params, SETUP, + CC_DRD_MODE_ENABLE); + } +} + +void brcm_usb_init_eohci(struct brcm_usb_init_params *params) +{ + u32 reg; + void __iomem *ctrl = params->ctrl_regs; + + if (USB_CTRL_MASK_FAMILY(params, USB_PM, USB20_HC_RESETB)) + USB_CTRL_SET_FAMILY(params, USB_PM, USB20_HC_RESETB); + + if (params->selected_family == BRCM_FAMILY_7366C0) + /* + * Don't enable this so the memory controller doesn't read + * into memory holes. NOTE: This bit is low true on 7366C0. + */ + USB_CTRL_SET_FAMILY(params, EBRIDGE, ESTOP_SCB_REQ); + + /* Setup the endian bits */ + reg = brcmusb_readl(USB_CTRL_REG(ctrl, SETUP)); + reg &= ~USB_CTRL_SETUP_ENDIAN_BITS; + reg |= USB_CTRL_MASK_FAMILY(params, SETUP, ENDIAN); + brcmusb_writel(reg, USB_CTRL_REG(ctrl, SETUP)); +} + +void brcm_usb_init_xhci(struct brcm_usb_init_params *params) +{ + void __iomem *ctrl = params->ctrl_regs; + + if (BRCM_ID(params->family_id) == 0x7366) { + /* + * The PHY3_SOFT_RESETB bits default to the wrong state. + */ + USB_CTRL_SET(ctrl, USB30_PCTL, PHY3_SOFT_RESETB); + USB_CTRL_SET(ctrl, USB30_PCTL, PHY3_SOFT_RESETB_P1); + } + + /* + * Kick start USB3 PHY + * Make sure it's low to insure a rising edge. + */ + USB_CTRL_UNSET(ctrl, USB30_CTL1, PHY3_PLL_SEQ_START); + USB_CTRL_SET(ctrl, USB30_CTL1, PHY3_PLL_SEQ_START); + + brcmusb_usb3_phy_workarounds(params); + brcmusb_xhci_soft_reset(params, 0); + brcmusb_usb3_otp_fix(params); +} + +void brcm_usb_uninit_common(struct brcm_usb_init_params *params) +{ + if (USB_CTRL_MASK_FAMILY(params, USB_PM, USB_PWRDN)) + USB_CTRL_SET_FAMILY(params, USB_PM, USB_PWRDN); + + if (USB_CTRL_MASK_FAMILY(params, PLL_CTL, PLL_IDDQ_PWRDN)) + USB_CTRL_SET_FAMILY(params, PLL_CTL, PLL_IDDQ_PWRDN); +} + +void brcm_usb_uninit_eohci(struct brcm_usb_init_params *params) +{ + if (USB_CTRL_MASK_FAMILY(params, USB_PM, USB20_HC_RESETB)) + USB_CTRL_UNSET_FAMILY(params, USB_PM, USB20_HC_RESETB); +} + +void brcm_usb_uninit_xhci(struct brcm_usb_init_params *params) +{ + brcmusb_xhci_soft_reset(params, 1); +} + +void brcm_usb_set_family_map(struct brcm_usb_init_params *params) +{ + int fam; + + fam = brcmusb_get_family_type(params); + params->selected_family = fam; + params->usb_reg_bits_map = + &usb_reg_bits_map_table[fam][0]; + params->family_name = family_names[fam]; +} diff --git a/drivers/phy/broadcom/phy-brcm-usb-init.h b/drivers/phy/broadcom/phy-brcm-usb-init.h new file mode 100644 index 000000000000..bb77b863885e --- /dev/null +++ b/drivers/phy/broadcom/phy-brcm-usb-init.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2014-2017 Broadcom + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _USB_BRCM_COMMON_INIT_H +#define _USB_BRCM_COMMON_INIT_H + +#define USB_CTLR_MODE_HOST 0 +#define USB_CTLR_MODE_DEVICE 1 +#define USB_CTLR_MODE_DRD 2 +#define USB_CTLR_MODE_TYPEC_PD 3 + +struct brcm_usb_init_params; + +struct brcm_usb_init_params { + void __iomem *ctrl_regs; + void __iomem *xhci_ec_regs; + int ioc; + int ipp; + int mode; + u32 family_id; + u32 product_id; + int selected_family; + const char *family_name; + const u32 *usb_reg_bits_map; +}; + +void brcm_usb_set_family_map(struct brcm_usb_init_params *params); +int brcm_usb_init_get_dual_select(struct brcm_usb_init_params *params); +void brcm_usb_init_set_dual_select(struct brcm_usb_init_params *params, + int mode); + +void brcm_usb_init_ipp(struct brcm_usb_init_params *ini); +void brcm_usb_init_common(struct brcm_usb_init_params *ini); +void brcm_usb_init_eohci(struct brcm_usb_init_params *ini); +void brcm_usb_init_xhci(struct brcm_usb_init_params *ini); +void brcm_usb_uninit_common(struct brcm_usb_init_params *ini); +void brcm_usb_uninit_eohci(struct brcm_usb_init_params *ini); +void brcm_usb_uninit_xhci(struct brcm_usb_init_params *ini); + +#endif /* _USB_BRCM_COMMON_INIT_H */ diff --git a/drivers/phy/broadcom/phy-brcm-usb.c b/drivers/phy/broadcom/phy-brcm-usb.c new file mode 100644 index 000000000000..195b98139e5f --- /dev/null +++ b/drivers/phy/broadcom/phy-brcm-usb.c @@ -0,0 +1,459 @@ +/* + * phy-brcm-usb.c - Broadcom USB Phy Driver + * + * Copyright (C) 2015-2017 Broadcom + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/soc/brcmstb/brcmstb.h> +#include <dt-bindings/phy/phy.h> + +#include "phy-brcm-usb-init.h" + +static DEFINE_MUTEX(sysfs_lock); + +enum brcm_usb_phy_id { + BRCM_USB_PHY_2_0 = 0, + BRCM_USB_PHY_3_0, + BRCM_USB_PHY_ID_MAX +}; + +struct value_to_name_map { + int value; + const char *name; +}; + +static struct value_to_name_map brcm_dr_mode_to_name[] = { + { USB_CTLR_MODE_HOST, "host" }, + { USB_CTLR_MODE_DEVICE, "peripheral" }, + { USB_CTLR_MODE_DRD, "drd" }, + { USB_CTLR_MODE_TYPEC_PD, "typec-pd" } +}; + +static struct value_to_name_map brcm_dual_mode_to_name[] = { + { 0, "host" }, + { 1, "device" }, + { 2, "auto" }, +}; + +struct brcm_usb_phy { + struct phy *phy; + unsigned int id; + bool inited; +}; + +struct brcm_usb_phy_data { + struct brcm_usb_init_params ini; + bool has_eohci; + bool has_xhci; + struct clk *usb_20_clk; + struct clk *usb_30_clk; + struct mutex mutex; /* serialize phy init */ + int init_count; + struct brcm_usb_phy phys[BRCM_USB_PHY_ID_MAX]; +}; + +static int brcm_usb_phy_init(struct phy *gphy) +{ + struct brcm_usb_phy *phy = phy_get_drvdata(gphy); + struct brcm_usb_phy_data *priv = + container_of(phy, struct brcm_usb_phy_data, phys[phy->id]); + + /* + * Use a lock to make sure a second caller waits until + * the base phy is inited before using it. + */ + mutex_lock(&priv->mutex); + if (priv->init_count++ == 0) { + clk_enable(priv->usb_20_clk); + clk_enable(priv->usb_30_clk); + brcm_usb_init_common(&priv->ini); + } + mutex_unlock(&priv->mutex); + if (phy->id == BRCM_USB_PHY_2_0) + brcm_usb_init_eohci(&priv->ini); + else if (phy->id == BRCM_USB_PHY_3_0) + brcm_usb_init_xhci(&priv->ini); + phy->inited = true; + dev_dbg(&gphy->dev, "INIT, id: %d, total: %d\n", phy->id, + priv->init_count); + + return 0; +} + +static int brcm_usb_phy_exit(struct phy *gphy) +{ + struct brcm_usb_phy *phy = phy_get_drvdata(gphy); + struct brcm_usb_phy_data *priv = + container_of(phy, struct brcm_usb_phy_data, phys[phy->id]); + + dev_dbg(&gphy->dev, "EXIT\n"); + if (phy->id == BRCM_USB_PHY_2_0) + brcm_usb_uninit_eohci(&priv->ini); + if (phy->id == BRCM_USB_PHY_3_0) + brcm_usb_uninit_xhci(&priv->ini); + + /* If both xhci and eohci are gone, reset everything else */ + mutex_lock(&priv->mutex); + if (--priv->init_count == 0) { + brcm_usb_uninit_common(&priv->ini); + clk_disable(priv->usb_20_clk); + clk_disable(priv->usb_30_clk); + } + mutex_unlock(&priv->mutex); + phy->inited = false; + return 0; +} + +static struct phy_ops brcm_usb_phy_ops = { + .init = brcm_usb_phy_init, + .exit = brcm_usb_phy_exit, + .owner = THIS_MODULE, +}; + +static struct phy *brcm_usb_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct brcm_usb_phy_data *data = dev_get_drvdata(dev); + + /* + * values 0 and 1 are for backward compatibility with + * device tree nodes from older bootloaders. + */ + switch (args->args[0]) { + case 0: + case PHY_TYPE_USB2: + if (data->phys[BRCM_USB_PHY_2_0].phy) + return data->phys[BRCM_USB_PHY_2_0].phy; + dev_warn(dev, "Error, 2.0 Phy not found\n"); + break; + case 1: + case PHY_TYPE_USB3: + if (data->phys[BRCM_USB_PHY_3_0].phy) + return data->phys[BRCM_USB_PHY_3_0].phy; + dev_warn(dev, "Error, 3.0 Phy not found\n"); + break; + } + return ERR_PTR(-ENODEV); +} + +static int name_to_value(struct value_to_name_map *table, int count, + const char *name, int *value) +{ + int x; + + *value = 0; + for (x = 0; x < count; x++) { + if (sysfs_streq(name, table[x].name)) { + *value = x; + return 0; + } + } + return -EINVAL; +} + +static const char *value_to_name(struct value_to_name_map *table, int count, + int value) +{ + if (value >= count) + return "unknown"; + return table[value].name; +} + +static ssize_t dr_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct brcm_usb_phy_data *priv = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", + value_to_name(&brcm_dr_mode_to_name[0], + ARRAY_SIZE(brcm_dr_mode_to_name), + priv->ini.mode)); +} +static DEVICE_ATTR_RO(dr_mode); + +static ssize_t dual_select_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct brcm_usb_phy_data *priv = dev_get_drvdata(dev); + int value; + int res; + + mutex_lock(&sysfs_lock); + res = name_to_value(&brcm_dual_mode_to_name[0], + ARRAY_SIZE(brcm_dual_mode_to_name), buf, &value); + if (!res) { + brcm_usb_init_set_dual_select(&priv->ini, value); + res = len; + } + mutex_unlock(&sysfs_lock); + return res; +} + +static ssize_t dual_select_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct brcm_usb_phy_data *priv = dev_get_drvdata(dev); + int value; + + mutex_lock(&sysfs_lock); + value = brcm_usb_init_get_dual_select(&priv->ini); + mutex_unlock(&sysfs_lock); + return sprintf(buf, "%s\n", + value_to_name(&brcm_dual_mode_to_name[0], + ARRAY_SIZE(brcm_dual_mode_to_name), + value)); +} +static DEVICE_ATTR_RW(dual_select); + +static struct attribute *brcm_usb_phy_attrs[] = { + &dev_attr_dr_mode.attr, + &dev_attr_dual_select.attr, + NULL +}; + +static const struct attribute_group brcm_usb_phy_group = { + .attrs = brcm_usb_phy_attrs, +}; + +static int brcm_usb_phy_dvr_init(struct device *dev, + struct brcm_usb_phy_data *priv, + struct device_node *dn) +{ + struct phy *gphy; + int err; + + priv->usb_20_clk = of_clk_get_by_name(dn, "sw_usb"); + if (IS_ERR(priv->usb_20_clk)) { + dev_info(dev, "Clock not found in Device Tree\n"); + priv->usb_20_clk = NULL; + } + err = clk_prepare_enable(priv->usb_20_clk); + if (err) + return err; + + if (priv->has_eohci) { + gphy = devm_phy_create(dev, NULL, &brcm_usb_phy_ops); + if (IS_ERR(gphy)) { + dev_err(dev, "failed to create EHCI/OHCI PHY\n"); + return PTR_ERR(gphy); + } + priv->phys[BRCM_USB_PHY_2_0].phy = gphy; + priv->phys[BRCM_USB_PHY_2_0].id = BRCM_USB_PHY_2_0; + phy_set_drvdata(gphy, &priv->phys[BRCM_USB_PHY_2_0]); + } + + if (priv->has_xhci) { + gphy = devm_phy_create(dev, NULL, &brcm_usb_phy_ops); + if (IS_ERR(gphy)) { + dev_err(dev, "failed to create XHCI PHY\n"); + return PTR_ERR(gphy); + } + priv->phys[BRCM_USB_PHY_3_0].phy = gphy; + priv->phys[BRCM_USB_PHY_3_0].id = BRCM_USB_PHY_3_0; + phy_set_drvdata(gphy, &priv->phys[BRCM_USB_PHY_3_0]); + + priv->usb_30_clk = of_clk_get_by_name(dn, "sw_usb3"); + if (IS_ERR(priv->usb_30_clk)) { + dev_info(dev, + "USB3.0 clock not found in Device Tree\n"); + priv->usb_30_clk = NULL; + } + err = clk_prepare_enable(priv->usb_30_clk); + if (err) + return err; + } + return 0; +} + +static int brcm_usb_phy_probe(struct platform_device *pdev) +{ + struct resource *res; + struct device *dev = &pdev->dev; + struct brcm_usb_phy_data *priv; + struct phy_provider *phy_provider; + struct device_node *dn = pdev->dev.of_node; + int err; + const char *mode; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + platform_set_drvdata(pdev, priv); + + priv->ini.family_id = brcmstb_get_family_id(); + priv->ini.product_id = brcmstb_get_product_id(); + brcm_usb_set_family_map(&priv->ini); + dev_dbg(dev, "Best mapping table is for %s\n", + priv->ini.family_name); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "can't get USB_CTRL base address\n"); + return -EINVAL; + } + priv->ini.ctrl_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->ini.ctrl_regs)) { + dev_err(dev, "can't map CTRL register space\n"); + return -EINVAL; + } + + /* The XHCI EC registers are optional */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + priv->ini.xhci_ec_regs = + devm_ioremap_resource(dev, res); + if (IS_ERR(priv->ini.xhci_ec_regs)) { + dev_err(dev, "can't map XHCI EC register space\n"); + return -EINVAL; + } + } + + of_property_read_u32(dn, "brcm,ipp", &priv->ini.ipp); + of_property_read_u32(dn, "brcm,ioc", &priv->ini.ioc); + + priv->ini.mode = USB_CTLR_MODE_HOST; + err = of_property_read_string(dn, "dr_mode", &mode); + if (err == 0) { + name_to_value(&brcm_dr_mode_to_name[0], + ARRAY_SIZE(brcm_dr_mode_to_name), + mode, &priv->ini.mode); + } + if (of_property_read_bool(dn, "brcm,has_xhci")) + priv->has_xhci = true; + if (of_property_read_bool(dn, "brcm,has_eohci")) + priv->has_eohci = true; + + err = brcm_usb_phy_dvr_init(dev, priv, dn); + if (err) + return err; + + mutex_init(&priv->mutex); + + /* make sure invert settings are correct */ + brcm_usb_init_ipp(&priv->ini); + + /* + * Create sysfs entries for mode. + * Remove "dual_select" attribute if not in dual mode + */ + if (priv->ini.mode != USB_CTLR_MODE_DRD) + brcm_usb_phy_attrs[1] = NULL; + err = sysfs_create_group(&dev->kobj, &brcm_usb_phy_group); + if (err) + dev_warn(dev, "Error creating sysfs attributes\n"); + + /* start with everything off */ + if (priv->has_xhci) + brcm_usb_uninit_xhci(&priv->ini); + if (priv->has_eohci) + brcm_usb_uninit_eohci(&priv->ini); + brcm_usb_uninit_common(&priv->ini); + clk_disable(priv->usb_20_clk); + clk_disable(priv->usb_30_clk); + + phy_provider = devm_of_phy_provider_register(dev, brcm_usb_phy_xlate); + if (IS_ERR(phy_provider)) + return PTR_ERR(phy_provider); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int brcm_usb_phy_suspend(struct device *dev) +{ + struct brcm_usb_phy_data *priv = dev_get_drvdata(dev); + + if (priv->init_count) { + clk_disable(priv->usb_20_clk); + clk_disable(priv->usb_30_clk); + } + return 0; +} + +static int brcm_usb_phy_resume(struct device *dev) +{ + struct brcm_usb_phy_data *priv = dev_get_drvdata(dev); + + clk_enable(priv->usb_20_clk); + clk_enable(priv->usb_30_clk); + brcm_usb_init_ipp(&priv->ini); + + /* + * Initialize anything that was previously initialized. + * Uninitialize anything that wasn't previously initialized. + */ + if (priv->init_count) { + brcm_usb_init_common(&priv->ini); + if (priv->phys[BRCM_USB_PHY_2_0].inited) { + brcm_usb_init_eohci(&priv->ini); + } else if (priv->has_eohci) { + brcm_usb_uninit_eohci(&priv->ini); + clk_disable(priv->usb_20_clk); + } + if (priv->phys[BRCM_USB_PHY_3_0].inited) { + brcm_usb_init_xhci(&priv->ini); + } else if (priv->has_xhci) { + brcm_usb_uninit_xhci(&priv->ini); + clk_disable(priv->usb_30_clk); + } + } else { + if (priv->has_xhci) + brcm_usb_uninit_xhci(&priv->ini); + if (priv->has_eohci) + brcm_usb_uninit_eohci(&priv->ini); + brcm_usb_uninit_common(&priv->ini); + clk_disable(priv->usb_20_clk); + clk_disable(priv->usb_30_clk); + } + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops brcm_usb_phy_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(brcm_usb_phy_suspend, brcm_usb_phy_resume) +}; + +static const struct of_device_id brcm_usb_dt_ids[] = { + { .compatible = "brcm,brcmstb-usb-phy" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, brcm_usb_dt_ids); + +static struct platform_driver brcm_usb_driver = { + .probe = brcm_usb_phy_probe, + .driver = { + .name = "brcmstb-usb-phy", + .owner = THIS_MODULE, + .pm = &brcm_usb_phy_pm_ops, + .of_match_table = brcm_usb_dt_ids, + }, +}; + +module_platform_driver(brcm_usb_driver); + +MODULE_ALIAS("platform:brcmstb-usb-phy"); +MODULE_AUTHOR("Al Cooper <acooper@broadcom.com>"); +MODULE_DESCRIPTION("BRCM USB PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/marvell/phy-mvebu-cp110-comphy.c b/drivers/phy/marvell/phy-mvebu-cp110-comphy.c index 89c887ea5557..a0d522154cdf 100644 --- a/drivers/phy/marvell/phy-mvebu-cp110-comphy.c +++ b/drivers/phy/marvell/phy-mvebu-cp110-comphy.c @@ -154,7 +154,6 @@ struct mvebu_comphy_priv { void __iomem *base; struct regmap *regmap; struct device *dev; - int modes[MVEBU_COMPHY_LANES]; }; struct mvebu_comphy_lane { diff --git a/drivers/phy/mediatek/phy-mtk-tphy.c b/drivers/phy/mediatek/phy-mtk-tphy.c index 721a2a1c97ef..402385f2562a 100644 --- a/drivers/phy/mediatek/phy-mtk-tphy.c +++ b/drivers/phy/mediatek/phy-mtk-tphy.c @@ -96,9 +96,11 @@ #define U3P_U2PHYDTM1 0x06C #define P2C_RG_UART_EN BIT(16) +#define P2C_FORCE_IDDIG BIT(9) #define P2C_RG_VBUSVALID BIT(5) #define P2C_RG_SESSEND BIT(4) #define P2C_RG_AVALID BIT(2) +#define P2C_RG_IDDIG BIT(1) #define U3P_U3_CHIP_GPIO_CTLD 0x0c #define P3C_REG_IP_SW_RST BIT(31) @@ -585,6 +587,31 @@ static void u2_phy_instance_exit(struct mtk_tphy *tphy, } } +static void u2_phy_instance_set_mode(struct mtk_tphy *tphy, + struct mtk_phy_instance *instance, + enum phy_mode mode) +{ + struct u2phy_banks *u2_banks = &instance->u2_banks; + u32 tmp; + + tmp = readl(u2_banks->com + U3P_U2PHYDTM1); + switch (mode) { + case PHY_MODE_USB_DEVICE: + tmp |= P2C_FORCE_IDDIG | P2C_RG_IDDIG; + break; + case PHY_MODE_USB_HOST: + tmp |= P2C_FORCE_IDDIG; + tmp &= ~P2C_RG_IDDIG; + break; + case PHY_MODE_USB_OTG: + tmp &= ~(P2C_FORCE_IDDIG | P2C_RG_IDDIG); + break; + default: + return; + } + writel(tmp, u2_banks->com + U3P_U2PHYDTM1); +} + static void pcie_phy_instance_init(struct mtk_tphy *tphy, struct mtk_phy_instance *instance) { @@ -881,6 +908,17 @@ static int mtk_phy_exit(struct phy *phy) return 0; } +static int mtk_phy_set_mode(struct phy *phy, enum phy_mode mode) +{ + struct mtk_phy_instance *instance = phy_get_drvdata(phy); + struct mtk_tphy *tphy = dev_get_drvdata(phy->dev.parent); + + if (instance->type == PHY_TYPE_USB2) + u2_phy_instance_set_mode(tphy, instance, mode); + + return 0; +} + static struct phy *mtk_phy_xlate(struct device *dev, struct of_phandle_args *args) { @@ -931,6 +969,7 @@ static const struct phy_ops mtk_tphy_ops = { .exit = mtk_phy_exit, .power_on = mtk_phy_power_on, .power_off = mtk_phy_power_off, + .set_mode = mtk_phy_set_mode, .owner = THIS_MODULE, }; diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index a268f4d6f3e9..b4964b067aec 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -372,6 +372,21 @@ int phy_reset(struct phy *phy) } EXPORT_SYMBOL_GPL(phy_reset); +int phy_calibrate(struct phy *phy) +{ + int ret; + + if (!phy || !phy->ops->calibrate) + return 0; + + mutex_lock(&phy->mutex); + ret = phy->ops->calibrate(phy); + mutex_unlock(&phy->mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phy_calibrate); + /** * _of_phy_get() - lookup and obtain a reference to a phy by phandle * @np: device_node for which to get the phy diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-i.h b/drivers/phy/qualcomm/phy-qcom-ufs-i.h index 13b02b7de30b..822c83b8efcd 100644 --- a/drivers/phy/qualcomm/phy-qcom-ufs-i.h +++ b/drivers/phy/qualcomm/phy-qcom-ufs-i.h @@ -114,14 +114,16 @@ struct ufs_qcom_phy { struct ufs_qcom_phy_calibration *cached_regs; int cached_regs_table_size; bool is_powered_on; + bool is_started; struct ufs_qcom_phy_specific_ops *phy_spec_ops; + + enum phy_mode mode; }; /** * struct 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 - * @calibrate_phy: pointer to a function that calibrate the phy * @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. @@ -130,7 +132,6 @@ struct ufs_qcom_phy { * and writes to QSERDES_RX_SIGDET_CNTRL attribute */ struct ufs_qcom_phy_specific_ops { - int (*calibrate_phy)(struct ufs_qcom_phy *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); diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c index 12a1b498dc4b..ba1895b76a5d 100644 --- a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c +++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c @@ -44,7 +44,19 @@ void ufs_qcom_phy_qmp_14nm_advertise_quirks(struct ufs_qcom_phy *phy_common) static int ufs_qcom_phy_qmp_14nm_init(struct phy *generic_phy) { - return 0; + struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy); + bool is_rate_B = false; + int ret; + + if (phy_common->mode == PHY_MODE_UFS_HS_B) + is_rate_B = true; + + ret = ufs_qcom_phy_qmp_14nm_phy_calibrate(phy_common, is_rate_B); + if (!ret) + /* phy calibrated, but yet to be started */ + phy_common->is_started = false; + + return ret; } static int ufs_qcom_phy_qmp_14nm_exit(struct phy *generic_phy) @@ -53,6 +65,19 @@ static int ufs_qcom_phy_qmp_14nm_exit(struct phy *generic_phy) } static +int ufs_qcom_phy_qmp_14nm_set_mode(struct phy *generic_phy, enum phy_mode mode) +{ + 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); @@ -102,11 +127,11 @@ static const struct phy_ops ufs_qcom_phy_qmp_14nm_phy_ops = { .exit = ufs_qcom_phy_qmp_14nm_exit, .power_on = ufs_qcom_phy_power_on, .power_off = ufs_qcom_phy_power_off, + .set_mode = ufs_qcom_phy_qmp_14nm_set_mode, .owner = THIS_MODULE, }; static struct ufs_qcom_phy_specific_ops phy_14nm_ops = { - .calibrate_phy = 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, diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c index 4f68acb58b73..49f435c71147 100644 --- a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c +++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c @@ -63,7 +63,19 @@ void ufs_qcom_phy_qmp_20nm_advertise_quirks(struct ufs_qcom_phy *phy_common) static int ufs_qcom_phy_qmp_20nm_init(struct phy *generic_phy) { - return 0; + struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy); + bool is_rate_B = false; + int ret; + + if (phy_common->mode == PHY_MODE_UFS_HS_B) + is_rate_B = true; + + ret = ufs_qcom_phy_qmp_20nm_phy_calibrate(phy_common, is_rate_B); + if (!ret) + /* phy calibrated, but yet to be started */ + phy_common->is_started = false; + + return ret; } static int ufs_qcom_phy_qmp_20nm_exit(struct phy *generic_phy) @@ -72,6 +84,19 @@ static int ufs_qcom_phy_qmp_20nm_exit(struct phy *generic_phy) } static +int ufs_qcom_phy_qmp_20nm_set_mode(struct phy *generic_phy, enum phy_mode mode) +{ + 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 & @@ -160,11 +185,11 @@ static const struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = { .exit = ufs_qcom_phy_qmp_20nm_exit, .power_on = ufs_qcom_phy_power_on, .power_off = ufs_qcom_phy_power_off, + .set_mode = ufs_qcom_phy_qmp_20nm_set_mode, .owner = THIS_MODULE, }; static struct ufs_qcom_phy_specific_ops phy_20nm_ops = { - .calibrate_phy = 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, diff --git a/drivers/phy/qualcomm/phy-qcom-ufs.c b/drivers/phy/qualcomm/phy-qcom-ufs.c index 43865ef340e2..c5ff4525edef 100644 --- a/drivers/phy/qualcomm/phy-qcom-ufs.c +++ b/drivers/phy/qualcomm/phy-qcom-ufs.c @@ -518,9 +518,8 @@ void ufs_qcom_phy_disable_iface_clk(struct ufs_qcom_phy *phy) } } -int ufs_qcom_phy_start_serdes(struct phy *generic_phy) +static int ufs_qcom_phy_start_serdes(struct ufs_qcom_phy *ufs_qcom_phy) { - struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy); int ret = 0; if (!ufs_qcom_phy->phy_spec_ops->start_serdes) { @@ -533,7 +532,6 @@ int ufs_qcom_phy_start_serdes(struct phy *generic_phy) return ret; } -EXPORT_SYMBOL_GPL(ufs_qcom_phy_start_serdes); int ufs_qcom_phy_set_tx_lane_enable(struct phy *generic_phy, u32 tx_lanes) { @@ -564,31 +562,8 @@ void ufs_qcom_phy_save_controller_version(struct phy *generic_phy, } EXPORT_SYMBOL_GPL(ufs_qcom_phy_save_controller_version); -int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy, bool is_rate_B) -{ - struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy); - int ret = 0; - - if (!ufs_qcom_phy->phy_spec_ops->calibrate_phy) { - dev_err(ufs_qcom_phy->dev, "%s: calibrate_phy() callback is not supported\n", - __func__); - ret = -ENOTSUPP; - } else { - ret = ufs_qcom_phy->phy_spec_ops-> - calibrate_phy(ufs_qcom_phy, is_rate_B); - if (ret) - dev_err(ufs_qcom_phy->dev, "%s: calibrate_phy() failed %d\n", - __func__, ret); - } - - return ret; -} -EXPORT_SYMBOL_GPL(ufs_qcom_phy_calibrate_phy); - -int ufs_qcom_phy_is_pcs_ready(struct phy *generic_phy) +static int ufs_qcom_phy_is_pcs_ready(struct ufs_qcom_phy *ufs_qcom_phy) { - struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_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__); @@ -598,7 +573,6 @@ int ufs_qcom_phy_is_pcs_ready(struct phy *generic_phy) return ufs_qcom_phy->phy_spec_ops-> is_physical_coding_sublayer_ready(ufs_qcom_phy); } -EXPORT_SYMBOL_GPL(ufs_qcom_phy_is_pcs_ready); int ufs_qcom_phy_power_on(struct phy *generic_phy) { @@ -609,6 +583,18 @@ int ufs_qcom_phy_power_on(struct phy *generic_phy) if (phy_common->is_powered_on) return 0; + if (!phy_common->is_started) { + 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; + + phy_common->is_started = true; + } + err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_phy); if (err) { dev_err(dev, "%s enable vdda_phy failed, err=%d\n", diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index 54c34298a000..479ae94b976e 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -1,7 +1,7 @@ /* * Renesas R-Car Gen3 for USB2.0 PHY driver * - * Copyright (C) 2015 Renesas Electronics Corporation + * Copyright (C) 2015-2017 Renesas Electronics Corporation * * This is based on the phy-rcar-gen2 driver: * Copyright (C) 2014 Renesas Solutions Corp. @@ -18,10 +18,12 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/of_address.h> +#include <linux/of_device.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> +#include <linux/usb/of.h> #include <linux/workqueue.h> /******* USB2.0 Host registers (original offset is +0x200) *******/ @@ -79,6 +81,8 @@ #define USB2_ADPCTRL_IDPULLUP BIT(5) /* 1 = ID sampling is enabled */ #define USB2_ADPCTRL_DRVVBUS BIT(4) +#define RCAR_GEN3_PHY_HAS_DEDICATED_PINS 1 + struct rcar_gen3_chan { void __iomem *base; struct extcon_dev *extcon; @@ -86,7 +90,7 @@ struct rcar_gen3_chan { struct regulator *vbus; struct work_struct work; bool extcon_host; - bool has_otg; + bool has_otg_pins; }; static void rcar_gen3_phy_usb2_work(struct work_struct *work) @@ -218,33 +222,40 @@ static bool rcar_gen3_is_host(struct rcar_gen3_chan *ch) return !(readl(ch->base + USB2_COMMCTRL) & USB2_COMMCTRL_OTG_PERI); } +static enum phy_mode rcar_gen3_get_phy_mode(struct rcar_gen3_chan *ch) +{ + if (rcar_gen3_is_host(ch)) + return PHY_MODE_USB_HOST; + + return PHY_MODE_USB_DEVICE; +} + static ssize_t role_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct rcar_gen3_chan *ch = dev_get_drvdata(dev); - bool is_b_device, is_host, new_mode_is_host; + bool is_b_device; + enum phy_mode cur_mode, new_mode; - if (!ch->has_otg || !ch->phy->init_count) + if (!ch->has_otg_pins || !ch->phy->init_count) return -EIO; - /* - * is_b_device: true is B-Device. false is A-Device. - * If {new_mode_}is_host: true is Host mode. false is Peripheral mode. - */ - is_b_device = rcar_gen3_check_id(ch); - is_host = rcar_gen3_is_host(ch); if (!strncmp(buf, "host", strlen("host"))) - new_mode_is_host = true; + new_mode = PHY_MODE_USB_HOST; else if (!strncmp(buf, "peripheral", strlen("peripheral"))) - new_mode_is_host = false; + new_mode = PHY_MODE_USB_DEVICE; else return -EINVAL; + /* is_b_device: true is B-Device. false is A-Device. */ + is_b_device = rcar_gen3_check_id(ch); + cur_mode = rcar_gen3_get_phy_mode(ch); + /* If current and new mode is the same, this returns the error */ - if (is_host == new_mode_is_host) + if (cur_mode == new_mode) return -EINVAL; - if (new_mode_is_host) { /* And is_host must be false */ + if (new_mode == PHY_MODE_USB_HOST) { /* And is_host must be false */ if (!is_b_device) /* A-Peripheral */ rcar_gen3_init_from_a_peri_to_a_host(ch); else /* B-Peripheral */ @@ -264,7 +275,7 @@ static ssize_t role_show(struct device *dev, struct device_attribute *attr, { struct rcar_gen3_chan *ch = dev_get_drvdata(dev); - if (!ch->has_otg || !ch->phy->init_count) + if (!ch->has_otg_pins || !ch->phy->init_count) return -EIO; return sprintf(buf, "%s\n", rcar_gen3_is_host(ch) ? "host" : @@ -303,7 +314,7 @@ static int rcar_gen3_phy_usb2_init(struct phy *p) writel(USB2_OC_TIMSET_INIT, usb2_base + USB2_OC_TIMSET); /* Initialize otg part */ - if (channel->has_otg) + if (channel->has_otg_pins) rcar_gen3_init_otg(channel); return 0; @@ -377,9 +388,17 @@ static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) } static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = { - { .compatible = "renesas,usb2-phy-r8a7795" }, - { .compatible = "renesas,usb2-phy-r8a7796" }, - { .compatible = "renesas,rcar-gen3-usb2-phy" }, + { + .compatible = "renesas,usb2-phy-r8a7795", + .data = (void *)RCAR_GEN3_PHY_HAS_DEDICATED_PINS, + }, + { + .compatible = "renesas,usb2-phy-r8a7796", + .data = (void *)RCAR_GEN3_PHY_HAS_DEDICATED_PINS, + }, + { + .compatible = "renesas,rcar-gen3-usb2-phy", + }, { } }; MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb2_match_table); @@ -415,14 +434,17 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) /* call request_irq for OTG */ irq = platform_get_irq(pdev, 0); if (irq >= 0) { - int ret; - 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); - channel->has_otg = true; + } + + if (of_usb_get_dr_mode_by_phy(dev->of_node, 0) == USB_DR_MODE_OTG) { + int ret; + + channel->has_otg_pins = (uintptr_t)of_device_get_match_data(dev); channel->extcon = devm_extcon_dev_allocate(dev, rcar_gen3_phy_cable); if (IS_ERR(channel->extcon)) @@ -464,7 +486,7 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) dev_err(dev, "Failed to register PHY provider\n"); ret = PTR_ERR(provider); goto error; - } else if (channel->has_otg) { + } else if (channel->has_otg_pins) { int ret; ret = device_create_file(dev, &dev_attr_role); @@ -484,7 +506,7 @@ static int rcar_gen3_phy_usb2_remove(struct platform_device *pdev) { struct rcar_gen3_chan *channel = platform_get_drvdata(pdev); - if (channel->has_otg) + if (channel->has_otg_pins) device_remove_file(&pdev->dev, &dev_attr_role); pm_runtime_disable(&pdev->dev); diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c index a958c9bced01..ee85fa0ca4b0 100644 --- a/drivers/phy/rockchip/phy-rockchip-typec.c +++ b/drivers/phy/rockchip/phy-rockchip-typec.c @@ -102,9 +102,40 @@ #define CMN_PLL1_SS_CTRL1 (0xb8 << 2) #define CMN_PLL1_SS_CTRL2 (0xb9 << 2) #define CMN_RXCAL_OVRD (0xd1 << 2) + #define CMN_TXPUCAL_CTRL (0xe0 << 2) #define CMN_TXPUCAL_OVRD (0xe1 << 2) +#define CMN_TXPDCAL_CTRL (0xf0 << 2) #define CMN_TXPDCAL_OVRD (0xf1 << 2) + +/* For CMN_TXPUCAL_CTRL, CMN_TXPDCAL_CTRL */ +#define CMN_TXPXCAL_START BIT(15) +#define CMN_TXPXCAL_DONE BIT(14) +#define CMN_TXPXCAL_NO_RESPONSE BIT(13) +#define CMN_TXPXCAL_CURRENT_RESPONSE BIT(12) + +#define CMN_TXPU_ADJ_CTRL (0x108 << 2) +#define CMN_TXPD_ADJ_CTRL (0x10c << 2) + +/* + * For CMN_TXPUCAL_CTRL, CMN_TXPDCAL_CTRL, + * CMN_TXPU_ADJ_CTRL, CMN_TXPDCAL_CTRL + * + * NOTE: some of these registers are documented to be 2's complement + * signed numbers, but then documented to be always positive. Weird. + * In such a case, using CMN_CALIB_CODE_POS() avoids the unnecessary + * sign extension. + */ +#define CMN_CALIB_CODE_WIDTH 7 +#define CMN_CALIB_CODE_OFFSET 0 +#define CMN_CALIB_CODE_MASK GENMASK(CMN_CALIB_CODE_WIDTH, 0) +#define CMN_CALIB_CODE(x) \ + sign_extend32((x) >> CMN_CALIB_CODE_OFFSET, CMN_CALIB_CODE_WIDTH) + +#define CMN_CALIB_CODE_POS_MASK GENMASK(CMN_CALIB_CODE_WIDTH - 1, 0) +#define CMN_CALIB_CODE_POS(x) \ + (((x) >> CMN_CALIB_CODE_OFFSET) & CMN_CALIB_CODE_POS_MASK) + #define CMN_DIAG_PLL0_FBH_OVRD (0x1c0 << 2) #define CMN_DIAG_PLL0_FBL_OVRD (0x1c1 << 2) #define CMN_DIAG_PLL0_OVRD (0x1c2 << 2) @@ -138,6 +169,15 @@ #define TX_TXCC_MGNFS_MULT_101(n) ((0x4055 | ((n) << 9)) << 2) #define TX_TXCC_MGNFS_MULT_110(n) ((0x4056 | ((n) << 9)) << 2) #define TX_TXCC_MGNFS_MULT_111(n) ((0x4057 | ((n) << 9)) << 2) +#define TX_TXCC_MGNLS_MULT_000(n) ((0x4058 | ((n) << 9)) << 2) +#define TX_TXCC_MGNLS_MULT_001(n) ((0x4059 | ((n) << 9)) << 2) +#define TX_TXCC_MGNLS_MULT_010(n) ((0x405a | ((n) << 9)) << 2) +#define TX_TXCC_MGNLS_MULT_011(n) ((0x405b | ((n) << 9)) << 2) +#define TX_TXCC_MGNLS_MULT_100(n) ((0x405c | ((n) << 9)) << 2) +#define TX_TXCC_MGNLS_MULT_101(n) ((0x405d | ((n) << 9)) << 2) +#define TX_TXCC_MGNLS_MULT_110(n) ((0x405e | ((n) << 9)) << 2) +#define TX_TXCC_MGNLS_MULT_111(n) ((0x405f | ((n) << 9)) << 2) + #define XCVR_DIAG_PLLDRC_CTRL(n) ((0x40e0 | ((n) << 9)) << 2) #define XCVR_DIAG_BIDI_CTRL(n) ((0x40e8 | ((n) << 9)) << 2) #define XCVR_DIAG_LANE_FCM_EN_MGN(n) ((0x40f2 | ((n) << 9)) << 2) @@ -150,10 +190,63 @@ #define TX_RCVDET_ST_TMR(n) ((0x4123 | ((n) << 9)) << 2) #define TX_DIAG_TX_DRV(n) ((0x41e1 | ((n) << 9)) << 2) #define TX_DIAG_BGREF_PREDRV_DELAY (0x41e7 << 2) + +/* Use this for "n" in macros like "_MULT_XXX" to target the aux channel */ +#define AUX_CH_LANE 8 + #define TX_ANA_CTRL_REG_1 (0x5020 << 2) + +#define TXDA_DP_AUX_EN BIT(15) +#define AUXDA_SE_EN BIT(14) +#define TXDA_CAL_LATCH_EN BIT(13) +#define AUXDA_POLARITY BIT(12) +#define TXDA_DRV_POWER_ISOLATION_EN BIT(11) +#define TXDA_DRV_POWER_EN_PH_2_N BIT(10) +#define TXDA_DRV_POWER_EN_PH_1_N BIT(9) +#define TXDA_BGREF_EN BIT(8) +#define TXDA_DRV_LDO_EN BIT(7) +#define TXDA_DECAP_EN_DEL BIT(6) +#define TXDA_DECAP_EN BIT(5) +#define TXDA_UPHY_SUPPLY_EN_DEL BIT(4) +#define TXDA_UPHY_SUPPLY_EN BIT(3) +#define TXDA_LOW_LEAKAGE_EN BIT(2) +#define TXDA_DRV_IDLE_LOWI_EN BIT(1) +#define TXDA_DRV_CMN_MODE_EN BIT(0) + #define TX_ANA_CTRL_REG_2 (0x5021 << 2) + +#define AUXDA_DEBOUNCING_CLK BIT(15) +#define TXDA_LPBK_RECOVERED_CLK_EN BIT(14) +#define TXDA_LPBK_ISI_GEN_EN BIT(13) +#define TXDA_LPBK_SERIAL_EN BIT(12) +#define TXDA_LPBK_LINE_EN BIT(11) +#define TXDA_DRV_LDO_REDC_SINKIQ BIT(10) +#define XCVR_DECAP_EN_DEL BIT(9) +#define XCVR_DECAP_EN BIT(8) +#define TXDA_MPHY_ENABLE_HS_NT BIT(7) +#define TXDA_MPHY_SA_MODE BIT(6) +#define TXDA_DRV_LDO_RBYR_FB_EN BIT(5) +#define TXDA_DRV_RST_PULL_DOWN BIT(4) +#define TXDA_DRV_LDO_BG_FB_EN BIT(3) +#define TXDA_DRV_LDO_BG_REF_EN BIT(2) +#define TXDA_DRV_PREDRV_EN_DEL BIT(1) +#define TXDA_DRV_PREDRV_EN BIT(0) + #define TXDA_COEFF_CALC_CTRL (0x5022 << 2) + +#define TX_HIGH_Z BIT(6) +#define TX_VMARGIN_OFFSET 3 +#define TX_VMARGIN_MASK 0x7 +#define LOW_POWER_SWING_EN BIT(2) +#define TX_FCM_DRV_MAIN_EN BIT(1) +#define TX_FCM_FULL_MARGIN BIT(0) + #define TX_DIG_CTRL_REG_2 (0x5024 << 2) + +#define TX_HIGH_Z_TM_EN BIT(15) +#define TX_RESCAL_CODE_OFFSET 0 +#define TX_RESCAL_CODE_MASK 0x3f + #define TXDA_CYA_AUXDA_CYA (0x5025 << 2) #define TX_ANA_CTRL_REG_3 (0x5026 << 2) #define TX_ANA_CTRL_REG_4 (0x5027 << 2) @@ -456,54 +549,72 @@ static void tcphy_dp_aux_set_flip(struct rockchip_typec_phy *tcphy) */ tx_ana_ctrl_reg_1 = readl(tcphy->base + TX_ANA_CTRL_REG_1); if (!tcphy->flip) - tx_ana_ctrl_reg_1 |= BIT(12); + tx_ana_ctrl_reg_1 |= AUXDA_POLARITY; else - tx_ana_ctrl_reg_1 &= ~BIT(12); + tx_ana_ctrl_reg_1 &= ~AUXDA_POLARITY; writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1); } static void tcphy_dp_aux_calibration(struct rockchip_typec_phy *tcphy) { + u16 val; u16 tx_ana_ctrl_reg_1; - u16 rdata, rdata2, val; - - /* disable txda_cal_latch_en for rewrite the calibration values */ - tx_ana_ctrl_reg_1 = readl(tcphy->base + TX_ANA_CTRL_REG_1); - tx_ana_ctrl_reg_1 &= ~BIT(13); - writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1); + u16 tx_ana_ctrl_reg_2; + s32 pu_calib_code, pd_calib_code; + s32 pu_adj, pd_adj; + u16 calib; /* - * read a resistor calibration code from CMN_TXPUCAL_CTRL[6:0] and - * write it to TX_DIG_CTRL_REG_2[6:0], and delay 1ms to make sure it - * works. + * Calculate calibration code as per docs: use an average of the + * pull down and pull up. Then add in adjustments. */ - rdata = readl(tcphy->base + TX_DIG_CTRL_REG_2); - rdata = rdata & 0xffc0; + val = readl(tcphy->base + CMN_TXPUCAL_CTRL); + pu_calib_code = CMN_CALIB_CODE_POS(val); + val = readl(tcphy->base + CMN_TXPDCAL_CTRL); + pd_calib_code = CMN_CALIB_CODE_POS(val); + val = readl(tcphy->base + CMN_TXPU_ADJ_CTRL); + pu_adj = CMN_CALIB_CODE(val); + val = readl(tcphy->base + CMN_TXPD_ADJ_CTRL); + pd_adj = CMN_CALIB_CODE(val); + calib = (pu_calib_code + pd_calib_code) / 2 + pu_adj + pd_adj; - rdata2 = readl(tcphy->base + CMN_TXPUCAL_CTRL); - rdata2 = rdata2 & 0x3f; + /* disable txda_cal_latch_en for rewrite the calibration values */ + tx_ana_ctrl_reg_1 = readl(tcphy->base + TX_ANA_CTRL_REG_1); + tx_ana_ctrl_reg_1 &= ~TXDA_CAL_LATCH_EN; + writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1); - val = rdata | rdata2; + /* write the calibration, then delay 10 ms as sample in docs */ + val = readl(tcphy->base + TX_DIG_CTRL_REG_2); + val &= ~(TX_RESCAL_CODE_MASK << TX_RESCAL_CODE_OFFSET); + val |= calib << TX_RESCAL_CODE_OFFSET; writel(val, tcphy->base + TX_DIG_CTRL_REG_2); - usleep_range(1000, 1050); + usleep_range(10000, 10050); /* * Enable signal for latch that sample and holds calibration values. * Activate this signal for 1 clock cycle to sample new calibration * values. */ - tx_ana_ctrl_reg_1 |= BIT(13); + tx_ana_ctrl_reg_1 |= TXDA_CAL_LATCH_EN; writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1); usleep_range(150, 200); /* set TX Voltage Level and TX Deemphasis to 0 */ writel(0, tcphy->base + PHY_DP_TX_CTL); + /* re-enable decap */ - writel(0x100, tcphy->base + TX_ANA_CTRL_REG_2); - writel(0x300, tcphy->base + TX_ANA_CTRL_REG_2); - tx_ana_ctrl_reg_1 |= BIT(3); + tx_ana_ctrl_reg_2 = XCVR_DECAP_EN; + writel(tx_ana_ctrl_reg_2, tcphy->base + TX_ANA_CTRL_REG_2); + udelay(1); + tx_ana_ctrl_reg_2 |= XCVR_DECAP_EN_DEL; + writel(tx_ana_ctrl_reg_2, tcphy->base + TX_ANA_CTRL_REG_2); + + writel(0, tcphy->base + TX_ANA_CTRL_REG_3); + + tx_ana_ctrl_reg_1 |= TXDA_UPHY_SUPPLY_EN; writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1); - tx_ana_ctrl_reg_1 |= BIT(4); + udelay(1); + tx_ana_ctrl_reg_1 |= TXDA_UPHY_SUPPLY_EN_DEL; writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1); writel(0, tcphy->base + TX_ANA_CTRL_REG_5); @@ -515,44 +626,66 @@ static void tcphy_dp_aux_calibration(struct rockchip_typec_phy *tcphy) writel(0x1001, tcphy->base + TX_ANA_CTRL_REG_4); /* re-enables Bandgap reference for LDO */ - tx_ana_ctrl_reg_1 |= BIT(7); + tx_ana_ctrl_reg_1 |= TXDA_DRV_LDO_EN; writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1); - tx_ana_ctrl_reg_1 |= BIT(8); + udelay(5); + tx_ana_ctrl_reg_1 |= TXDA_BGREF_EN; writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1); /* * re-enables the transmitter pre-driver, driver data selection MUX, * and receiver detect circuits. */ - writel(0x301, tcphy->base + TX_ANA_CTRL_REG_2); - writel(0x303, tcphy->base + TX_ANA_CTRL_REG_2); + tx_ana_ctrl_reg_2 |= TXDA_DRV_PREDRV_EN; + writel(tx_ana_ctrl_reg_2, tcphy->base + TX_ANA_CTRL_REG_2); + udelay(1); + tx_ana_ctrl_reg_2 |= TXDA_DRV_PREDRV_EN_DEL; + writel(tx_ana_ctrl_reg_2, tcphy->base + TX_ANA_CTRL_REG_2); /* - * Do some magic undocumented stuff, some of which appears to - * undo the "re-enables Bandgap reference for LDO" above. + * Do all the undocumented magic: + * - Turn on TXDA_DP_AUX_EN, whatever that is, even though sample + * never shows this going on. + * - Turn on TXDA_DECAP_EN (and TXDA_DECAP_EN_DEL) even though + * docs say for aux it's always 0. + * - Turn off the LDO and BGREF, which we just spent time turning + * on above (???). + * + * Without this magic, things seem worse. */ - tx_ana_ctrl_reg_1 |= BIT(15); - tx_ana_ctrl_reg_1 &= ~BIT(8); - tx_ana_ctrl_reg_1 &= ~BIT(7); - tx_ana_ctrl_reg_1 |= BIT(6); - tx_ana_ctrl_reg_1 |= BIT(5); + tx_ana_ctrl_reg_1 |= TXDA_DP_AUX_EN; + tx_ana_ctrl_reg_1 |= TXDA_DECAP_EN; + tx_ana_ctrl_reg_1 &= ~TXDA_DRV_LDO_EN; + tx_ana_ctrl_reg_1 &= ~TXDA_BGREF_EN; + writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1); + udelay(1); + tx_ana_ctrl_reg_1 |= TXDA_DECAP_EN_DEL; writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1); - - writel(0, tcphy->base + TX_ANA_CTRL_REG_3); - writel(0, tcphy->base + TX_ANA_CTRL_REG_4); - writel(0, tcphy->base + TX_ANA_CTRL_REG_5); /* - * Controls low_power_swing_en, don't set the voltage swing of the - * driver to 400mv. The values below are peak to peak (differential) - * values. + * Undo the work we did to set the LDO voltage. + * This doesn't seem to help nor hurt, but it kinda goes with the + * undocumented magic above. */ + writel(0, tcphy->base + TX_ANA_CTRL_REG_4); + + /* Don't set voltage swing to 400 mV peak to peak (differential) */ writel(0, tcphy->base + TXDA_COEFF_CALC_CTRL); + + /* Init TXDA_CYA_AUXDA_CYA for unknown magic reasons */ writel(0, tcphy->base + TXDA_CYA_AUXDA_CYA); - /* Controls tx_high_z_tm_en */ + /* + * More undocumented magic, presumably the goal of which is to + * make the "auxda_source_aux_oen" be ignored and instead to decide + * about "high impedance state" based on what software puts in the + * register TXDA_COEFF_CALC_CTRL (see TX_HIGH_Z). Since we only + * program that register once and we don't set the bit TX_HIGH_Z, + * presumably the goal here is that we should never put the analog + * driver in high impedance state. + */ val = readl(tcphy->base + TX_DIG_CTRL_REG_2); - val |= BIT(15); + val |= TX_HIGH_Z_TM_EN; writel(val, tcphy->base + TX_DIG_CTRL_REG_2); } diff --git a/drivers/phy/ti/phy-ti-pipe3.c b/drivers/phy/ti/phy-ti-pipe3.c index 0e564f32749f..68ce4a082b9b 100644 --- a/drivers/phy/ti/phy-ti-pipe3.c +++ b/drivers/phy/ti/phy-ti-pipe3.c @@ -68,6 +68,40 @@ #define PCIE_PCS_MASK 0xFF0000 #define PCIE_PCS_DELAY_COUNT_SHIFT 0x10 +#define PCIEPHYRX_ANA_PROGRAMMABILITY 0x0000000C +#define INTERFACE_MASK GENMASK(31, 27) +#define INTERFACE_SHIFT 27 +#define LOSD_MASK GENMASK(17, 14) +#define LOSD_SHIFT 14 +#define MEM_PLLDIV GENMASK(6, 5) + +#define PCIEPHYRX_TRIM 0x0000001C +#define MEM_DLL_TRIM_SEL GENMASK(31, 30) +#define MEM_DLL_TRIM_SHIFT 30 + +#define PCIEPHYRX_DLL 0x00000024 +#define MEM_DLL_PHINT_RATE GENMASK(31, 30) + +#define PCIEPHYRX_DIGITAL_MODES 0x00000028 +#define MEM_CDR_FASTLOCK BIT(23) +#define MEM_CDR_LBW GENMASK(22, 21) +#define MEM_CDR_STEPCNT GENMASK(20, 19) +#define MEM_CDR_STL_MASK GENMASK(18, 16) +#define MEM_CDR_STL_SHIFT 16 +#define MEM_CDR_THR_MASK GENMASK(15, 13) +#define MEM_CDR_THR_SHIFT 13 +#define MEM_CDR_THR_MODE BIT(12) +#define MEM_CDR_CDR_2NDO_SDM_MODE BIT(11) +#define MEM_OVRD_HS_RATE BIT(26) + +#define PCIEPHYRX_EQUALIZER 0x00000038 +#define MEM_EQLEV GENMASK(31, 16) +#define MEM_EQFTC GENMASK(15, 11) +#define MEM_EQCTL GENMASK(10, 7) +#define MEM_EQCTL_SHIFT 7 +#define MEM_OVRD_EQLEV BIT(2) +#define MEM_OVRD_EQFTC BIT(1) + /* * This is an Empirical value that works, need to confirm the actual * value required for the PIPE3PHY_PLL_CONFIGURATION2.PLL_IDLE status @@ -91,6 +125,8 @@ struct pipe3_dpll_map { struct ti_pipe3 { void __iomem *pll_ctrl_base; + void __iomem *phy_rx; + void __iomem *phy_tx; struct device *dev; struct device *control_dev; struct clk *wkupclk; @@ -261,6 +297,37 @@ static int ti_pipe3_dpll_program(struct ti_pipe3 *phy) return ti_pipe3_dpll_wait_lock(phy); } +static void ti_pipe3_calibrate(struct ti_pipe3 *phy) +{ + u32 val; + + val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_ANA_PROGRAMMABILITY); + val &= ~(INTERFACE_MASK | LOSD_MASK | MEM_PLLDIV); + val = (0x1 << INTERFACE_SHIFT | 0xA << LOSD_SHIFT); + ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_ANA_PROGRAMMABILITY, val); + + val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_DIGITAL_MODES); + val &= ~(MEM_CDR_STEPCNT | MEM_CDR_STL_MASK | MEM_CDR_THR_MASK | + MEM_CDR_CDR_2NDO_SDM_MODE | MEM_OVRD_HS_RATE); + val |= (MEM_CDR_FASTLOCK | MEM_CDR_LBW | 0x3 << MEM_CDR_STL_SHIFT | + 0x1 << MEM_CDR_THR_SHIFT | MEM_CDR_THR_MODE); + ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_DIGITAL_MODES, val); + + val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_TRIM); + val &= ~MEM_DLL_TRIM_SEL; + val |= 0x2 << MEM_DLL_TRIM_SHIFT; + ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_TRIM, val); + + val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_DLL); + val |= MEM_DLL_PHINT_RATE; + ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_DLL, val); + + val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_EQUALIZER); + val &= ~(MEM_EQLEV | MEM_EQCTL | MEM_OVRD_EQLEV | MEM_OVRD_EQFTC); + val |= MEM_EQFTC | 0x1 << MEM_EQCTL_SHIFT; + ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_EQUALIZER, val); +} + static int ti_pipe3_init(struct phy *x) { struct ti_pipe3 *phy = phy_get_drvdata(x); @@ -282,7 +349,12 @@ static int ti_pipe3_init(struct phy *x) val = 0x96 << OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT; ret = regmap_update_bits(phy->pcs_syscon, phy->pcie_pcs_reg, PCIE_PCS_MASK, val); - return ret; + if (ret) + return ret; + + ti_pipe3_calibrate(phy); + + return 0; } /* Bring it out of IDLE if it is IDLE */ @@ -513,6 +585,29 @@ static int ti_pipe3_get_sysctrl(struct ti_pipe3 *phy) return 0; } +static int ti_pipe3_get_tx_rx_base(struct ti_pipe3 *phy) +{ + struct resource *res; + struct device *dev = phy->dev; + struct device_node *node = dev->of_node; + struct platform_device *pdev = to_platform_device(dev); + + if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie")) + return 0; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "phy_rx"); + phy->phy_rx = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->phy_rx)) + return PTR_ERR(phy->phy_rx); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "phy_tx"); + phy->phy_tx = devm_ioremap_resource(dev, res); + + return PTR_ERR_OR_ZERO(phy->phy_tx); +} + static int ti_pipe3_get_pll_base(struct ti_pipe3 *phy) { struct resource *res; @@ -559,6 +654,10 @@ static int ti_pipe3_probe(struct platform_device *pdev) if (ret) return ret; + ret = ti_pipe3_get_tx_rx_base(phy); + if (ret) + return ret; + ret = ti_pipe3_get_sysctrl(phy); if (ret) return ret; diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c index c87d770b519a..890eafeb8ad4 100644 --- a/drivers/scsi/ufs/ufs-qcom.c +++ b/drivers/scsi/ufs/ufs-qcom.c @@ -273,15 +273,18 @@ static int ufs_qcom_power_up_sequence(struct ufs_hba *hba) bool is_rate_B = (UFS_QCOM_LIMIT_HS_RATE == PA_HS_MODE_B) ? true : false; + if (is_rate_B) + phy_set_mode(phy, PHY_MODE_UFS_HS_B); + /* Assert PHY reset and apply PHY calibration values */ ufs_qcom_assert_reset(hba); /* provide 1ms delay to let the reset pulse propagate */ usleep_range(1000, 1100); - ret = ufs_qcom_phy_calibrate_phy(phy, is_rate_B); - + /* phy initialization - calibrate the phy */ + ret = phy_init(phy); if (ret) { - dev_err(hba->dev, "%s: ufs_qcom_phy_calibrate_phy() failed, ret = %d\n", + dev_err(hba->dev, "%s: phy init failed, ret = %d\n", __func__, ret); goto out; } @@ -294,21 +297,22 @@ static int ufs_qcom_power_up_sequence(struct ufs_hba *hba) * voltage, current to settle down before starting serdes. */ usleep_range(1000, 1100); - ret = ufs_qcom_phy_start_serdes(phy); + + /* power on phy - start serdes and phy's power and clocks */ + ret = phy_power_on(phy); if (ret) { - dev_err(hba->dev, "%s: ufs_qcom_phy_start_serdes() failed, ret = %d\n", + dev_err(hba->dev, "%s: phy power on failed, ret = %d\n", __func__, ret); - goto out; + goto out_disable_phy; } - ret = ufs_qcom_phy_is_pcs_ready(phy); - if (ret) - dev_err(hba->dev, - "%s: is_physical_coding_sublayer_ready() failed, ret = %d\n", - __func__, ret); - ufs_qcom_select_unipro_mode(host); + return 0; + +out_disable_phy: + ufs_qcom_assert_reset(hba); + phy_exit(phy); out: return ret; } @@ -1273,14 +1277,9 @@ static int ufs_qcom_init(struct ufs_hba *hba) ufs_qcom_phy_save_controller_version(host->generic_phy, host->hw_ver.major, host->hw_ver.minor, host->hw_ver.step); - phy_init(host->generic_phy); - err = phy_power_on(host->generic_phy); - if (err) - goto out_unregister_bus; - err = ufs_qcom_init_lane_clks(host); if (err) - goto out_disable_phy; + goto out_variant_clear; ufs_qcom_set_caps(hba); ufs_qcom_advertise_quirks(hba); @@ -1301,10 +1300,6 @@ static int ufs_qcom_init(struct ufs_hba *hba) goto out; -out_disable_phy: - phy_power_off(host->generic_phy); -out_unregister_bus: - phy_exit(host->generic_phy); out_variant_clear: ufshcd_set_variant(hba, NULL); out: diff --git a/drivers/soc/bcm/brcmstb/common.c b/drivers/soc/bcm/brcmstb/common.c index 22e98a90468c..a71730da6385 100644 --- a/drivers/soc/bcm/brcmstb/common.c +++ b/drivers/soc/bcm/brcmstb/common.c @@ -40,6 +40,18 @@ bool soc_is_brcmstb(void) return of_match_node(brcmstb_machine_match, root) != NULL; } +u32 brcmstb_get_family_id(void) +{ + return family_id; +} +EXPORT_SYMBOL(brcmstb_get_family_id); + +u32 brcmstb_get_product_id(void) +{ + return product_id; +} +EXPORT_SYMBOL(brcmstb_get_product_id); + static const struct of_device_id sun_top_ctrl_match[] = { { .compatible = "brcm,bcm7125-sun-top-ctrl", }, { .compatible = "brcm,bcm7346-sun-top-ctrl", }, diff --git a/include/dt-bindings/phy/phy.h b/include/dt-bindings/phy/phy.h index 6c901930eb3e..d16e8755f6a9 100644 --- a/include/dt-bindings/phy/phy.h +++ b/include/dt-bindings/phy/phy.h @@ -15,5 +15,6 @@ #define PHY_TYPE_PCIE 2 #define PHY_TYPE_USB2 3 #define PHY_TYPE_USB3 4 +#define PHY_TYPE_UFS 5 #endif /* _DT_BINDINGS_PHY */ diff --git a/include/linux/phy/phy-qcom-ufs.h b/include/linux/phy/phy-qcom-ufs.h index 35c070ea6ea3..0a2c18a9771d 100644 --- a/include/linux/phy/phy-qcom-ufs.h +++ b/include/linux/phy/phy-qcom-ufs.h @@ -31,10 +31,7 @@ void ufs_qcom_phy_enable_dev_ref_clk(struct phy *phy); */ void ufs_qcom_phy_disable_dev_ref_clk(struct phy *phy); -int ufs_qcom_phy_start_serdes(struct phy *phy); int ufs_qcom_phy_set_tx_lane_enable(struct phy *phy, u32 tx_lanes); -int ufs_qcom_phy_calibrate_phy(struct phy *phy, bool is_rate_B); -int ufs_qcom_phy_is_pcs_ready(struct phy *phy); void ufs_qcom_phy_save_controller_version(struct phy *phy, u8 major, u16 minor, u16 step); diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h index e694d4008c4a..4f8423a948d5 100644 --- a/include/linux/phy/phy.h +++ b/include/linux/phy/phy.h @@ -29,6 +29,8 @@ enum phy_mode { PHY_MODE_USB_OTG, PHY_MODE_SGMII, PHY_MODE_10GKR, + PHY_MODE_UFS_HS_A, + PHY_MODE_UFS_HS_B, }; /** @@ -39,6 +41,7 @@ enum phy_mode { * @power_off: powering off the phy * @set_mode: set the mode of the phy * @reset: resetting the phy + * @calibrate: calibrate the phy * @owner: the module owner containing the ops */ struct phy_ops { @@ -48,6 +51,7 @@ struct phy_ops { int (*power_off)(struct phy *phy); int (*set_mode)(struct phy *phy, enum phy_mode mode); int (*reset)(struct phy *phy); + int (*calibrate)(struct phy *phy); struct module *owner; }; @@ -141,6 +145,7 @@ int phy_power_on(struct phy *phy); int phy_power_off(struct phy *phy); int phy_set_mode(struct phy *phy, enum phy_mode mode); int phy_reset(struct phy *phy); +int phy_calibrate(struct phy *phy); static inline int phy_get_bus_width(struct phy *phy) { return phy->attrs.bus_width; @@ -262,6 +267,13 @@ static inline int phy_reset(struct phy *phy) return -ENOSYS; } +static inline int phy_calibrate(struct phy *phy) +{ + if (!phy) + return 0; + return -ENOSYS; +} + static inline int phy_get_bus_width(struct phy *phy) { return -ENOSYS; @@ -291,7 +303,7 @@ static inline struct phy *devm_phy_get(struct device *dev, const char *string) static inline struct phy *devm_phy_optional_get(struct device *dev, const char *string) { - return ERR_PTR(-ENOSYS); + return NULL; } static inline struct phy *devm_of_phy_get(struct device *dev, diff --git a/include/linux/soc/brcmstb/brcmstb.h b/include/linux/soc/brcmstb/brcmstb.h index 337ce414e898..8baafd6908b9 100644 --- a/include/linux/soc/brcmstb/brcmstb.h +++ b/include/linux/soc/brcmstb/brcmstb.h @@ -1,10 +1,27 @@ #ifndef __BRCMSTB_SOC_H #define __BRCMSTB_SOC_H +static inline u32 BRCM_ID(u32 reg) +{ + return reg >> 28 ? reg >> 16 : reg >> 8; +} + +static inline u32 BRCM_REV(u32 reg) +{ + return reg & 0xff; +} + /* * Bus Interface Unit control register setup, must happen early during boot, * before SMP is brought up, called by machine entry point. */ void brcmstb_biuctrl_init(void); +/* + * Helper functions for getting family or product id from the + * SoC driver. + */ +u32 brcmstb_get_family_id(void); +u32 brcmstb_get_product_id(void); + #endif /* __BRCMSTB_SOC_H */ |