diff options
author | Kishon Vijay Abraham I <kishon@ti.com> | 2016-09-15 13:16:11 +0300 |
---|---|---|
committer | Kishon Vijay Abraham I <kishon@ti.com> | 2016-09-15 14:15:20 +0300 |
commit | cb9850d092f7d2c86662d71fbfe10685cbddda21 (patch) | |
tree | 77023cbc002680edbe3738f87dc9be413a8b8e68 | |
parent | 81522637485dd6ec9de4279c9714d58f884b6091 (diff) | |
parent | b78ea84a7d45b9e5ad2eee429a2140065a39d755 (diff) | |
download | linux-cb9850d092f7d2c86662d71fbfe10685cbddda21.tar.xz |
Merge branch 'next' into resolution
Conflicts:
drivers/extcon/extcon-adc-jack.c
drivers/extcon/extcon-arizona.c
drivers/extcon/extcon-gpio.c
include/linux/extcon.h
28 files changed, 2877 insertions, 148 deletions
diff --git a/Documentation/devicetree/bindings/phy/bcm-ns-usb3-phy.txt b/Documentation/devicetree/bindings/phy/bcm-ns-usb3-phy.txt new file mode 100644 index 000000000000..09aeba94538d --- /dev/null +++ b/Documentation/devicetree/bindings/phy/bcm-ns-usb3-phy.txt @@ -0,0 +1,23 @@ +Driver for Broadcom Northstar USB 3.0 PHY + +Required properties: + +- compatible: one of: "brcm,ns-ax-usb3-phy", "brcm,ns-bx-usb3-phy". +- reg: register mappings for DMP (Device Management Plugin) and ChipCommon B + MMI. +- reg-names: "dmp" and "ccb-mii" + +Initialization of USB 3.0 PHY depends on Northstar version. There are currently +three known series: Ax, Bx and Cx. +Known A0: BCM4707 rev 0 +Known B0: BCM4707 rev 4, BCM53573 rev 2 +Known B1: BCM4707 rev 6 +Known C0: BCM47094 rev 0 + +Example: + usb3-phy { + compatible = "brcm,ns-ax-usb3-phy"; + reg = <0x18105000 0x1000>, <0x18003000 0x1000>; + reg-names = "dmp", "ccb-mii"; + #phy-cells = <0>; + }; diff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb2.txt b/Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb2.txt new file mode 100644 index 000000000000..3c29c77a7018 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb2.txt @@ -0,0 +1,64 @@ +ROCKCHIP USB2.0 PHY WITH INNO IP BLOCK + +Required properties (phy (parent) node): + - compatible : should be one of the listed compatibles: + * "rockchip,rk3366-usb2phy" + * "rockchip,rk3399-usb2phy" + - reg : the address offset of grf for usb-phy configuration. + - #clock-cells : should be 0. + - clock-output-names : specify the 480m output clock name. + +Optional properties: + - clocks : phandle + phy specifier pair, for the input clock of phy. + - clock-names : input clock name of phy, must be "phyclk". + +Required nodes : a sub-node is required for each port the phy provides. + The sub-node name is used to identify host or otg port, + and shall be the following entries: + * "otg-port" : the name of otg port. + * "host-port" : the name of host port. + +Required properties (port (child) node): + - #phy-cells : must be 0. See ./phy-bindings.txt for details. + - interrupts : specify an interrupt for each entry in interrupt-names. + - interrupt-names : a list which shall be the following entries: + * "otg-id" : for the otg id interrupt. + * "otg-bvalid" : for the otg vbus interrupt. + * "linestate" : for the host/otg linestate interrupt. + +Optional properties: + - phy-supply : phandle to a regulator that provides power to VBUS. + See ./phy-bindings.txt for details. + +Example: + +grf: syscon@ff770000 { + compatible = "rockchip,rk3366-grf", "syscon", "simple-mfd"; + #address-cells = <1>; + #size-cells = <1>; + +... + + u2phy: usb2-phy@700 { + compatible = "rockchip,rk3366-usb2phy"; + reg = <0x700 0x2c>; + #clock-cells = <0>; + clock-output-names = "sclk_otgphy0_480m"; + + u2phy_otg: otg-port { + #phy-cells = <0>; + interrupts = <GIC_SPI 93 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 94 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 95 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "otg-id", "otg-bvalid", "linestate"; + status = "okay"; + }; + + u2phy_host: host-port { + #phy-cells = <0>; + interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "linestate"; + status = "okay"; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt b/Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt new file mode 100644 index 000000000000..6ea867e3176f --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt @@ -0,0 +1,101 @@ +* ROCKCHIP type-c PHY +--------------------- + +Required properties: + - compatible : must be "rockchip,rk3399-typec-phy" + - reg: Address and length of the usb phy control register set + - rockchip,grf : phandle to the syscon managing the "general + register files" + - clocks : phandle + clock specifier for the phy clocks + - clock-names : string, clock name, must be "tcpdcore", "tcpdphy-ref"; + - assigned-clocks: main clock, should be <&cru SCLK_UPHY0_TCPDCORE> or + <&cru SCLK_UPHY1_TCPDCORE>; + - assigned-clock-rates : the phy core clk frequency, shall be: 50000000 + - resets : a list of phandle + reset specifier pairs + - reset-names : string reset name, must be: + "uphy", "uphy-pipe", "uphy-tcphy" + - extcon : extcon specifier for the Power Delivery + +Note, there are 2 type-c phys for RK3399, and they are almost identical, except +these registers(description below), every register node contains 3 sections: +offset, enable bit, write mask bit. + - rockchip,typec-conn-dir : the register of type-c connector direction, + for type-c phy0, it must be <0xe580 0 16>; + for type-c phy1, it must be <0xe58c 0 16>; + - rockchip,usb3tousb2-en : the register of type-c force usb3 to usb2 enable + control. + for type-c phy0, it must be <0xe580 3 19>; + for type-c phy1, it must be <0xe58c 3 19>; + - rockchip,external-psm : the register of type-c phy external psm clock + selection. + for type-c phy0, it must be <0xe588 14 30>; + for type-c phy1, it must be <0xe594 14 30>; + - rockchip,pipe-status : the register of type-c phy pipe status. + for type-c phy0, it must be <0xe5c0 0 0>; + for type-c phy1, it must be <0xe5c0 16 16>; + +Required nodes : a sub-node is required for each port the phy provides. + The sub-node name is used to identify dp or usb3 port, + and shall be the following entries: + * "dp-port" : the name of DP port. + * "usb3-port" : the name of USB3 port. + +Required properties (port (child) node): +- #phy-cells : must be 0, See ./phy-bindings.txt for details. + +Example: + tcphy0: phy@ff7c0000 { + compatible = "rockchip,rk3399-typec-phy"; + reg = <0x0 0xff7c0000 0x0 0x40000>; + rockchip,grf = <&grf>; + extcon = <&fusb0>; + clocks = <&cru SCLK_UPHY0_TCPDCORE>, + <&cru SCLK_UPHY0_TCPDPHY_REF>; + clock-names = "tcpdcore", "tcpdphy-ref"; + assigned-clocks = <&cru SCLK_UPHY0_TCPDCORE>; + assigned-clock-rates = <50000000>; + resets = <&cru SRST_UPHY0>, + <&cru SRST_UPHY0_PIPE_L00>, + <&cru SRST_P_UPHY0_TCPHY>; + reset-names = "uphy", "uphy-pipe", "uphy-tcphy"; + rockchip,typec-conn-dir = <0xe580 0 16>; + rockchip,usb3tousb2-en = <0xe580 3 19>; + rockchip,external-psm = <0xe588 14 30>; + rockchip,pipe-status = <0xe5c0 0 0>; + + tcphy0_dp: dp-port { + #phy-cells = <0>; + }; + + tcphy0_usb3: usb3-port { + #phy-cells = <0>; + }; + }; + + tcphy1: phy@ff800000 { + compatible = "rockchip,rk3399-typec-phy"; + reg = <0x0 0xff800000 0x0 0x40000>; + rockchip,grf = <&grf>; + extcon = <&fusb1>; + clocks = <&cru SCLK_UPHY1_TCPDCORE>, + <&cru SCLK_UPHY1_TCPDPHY_REF>; + clock-names = "tcpdcore", "tcpdphy-ref"; + assigned-clocks = <&cru SCLK_UPHY1_TCPDCORE>; + assigned-clock-rates = <50000000>; + resets = <&cru SRST_UPHY1>, + <&cru SRST_UPHY1_PIPE_L00>, + <&cru SRST_P_UPHY1_TCPHY>; + reset-names = "uphy", "uphy-pipe", "uphy-tcphy"; + rockchip,typec-conn-dir = <0xe58c 0 16>; + rockchip,usb3tousb2-en = <0xe58c 3 19>; + rockchip,external-psm = <0xe594 14 30>; + rockchip,pipe-status = <0xe5c0 16 16>; + + tcphy1_dp: dp-port { + #phy-cells = <0>; + }; + + tcphy1_usb3: usb3-port { + #phy-cells = <0>; + }; + }; diff --git a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt index 2281d6cdecb1..ace9cce2704a 100644 --- a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt +++ b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt @@ -6,6 +6,8 @@ This file provides information on what the device node for the R-Car generation Required properties: - compatible: "renesas,usb2-phy-r8a7795" if the device is a part of an R8A7795 SoC. + "renesas,usb2-phy-r8a7796" if the device is a part of an R8A7796 + SoC. "renesas,rcar-gen3-usb2-phy" for a generic R-Car Gen3 compatible device. When compatible with the generic version, nodes must list the @@ -30,11 +32,11 @@ Example (R-Car H3): compatible = "renesas,usb2-phy-r8a7795", "renesas,rcar-gen3-usb2-phy"; reg = <0 0xee080200 0 0x700>; interrupts = <GIC_SPI 108 IRQ_TYPE_LEVEL_HIGH>; - clocks = <&mstp7_clks R8A7795_CLK_EHCI0>; + clocks = <&cpg CPG_MOD 703>; }; usb-phy@ee0a0200 { compatible = "renesas,usb2-phy-r8a7795", "renesas,rcar-gen3-usb2-phy"; reg = <0 0xee0a0200 0 0x700>; - clocks = <&mstp7_clks R8A7795_CLK_EHCI0>; + clocks = <&cpg CPG_MOD 702>; }; diff --git a/Documentation/devicetree/bindings/phy/rockchip-pcie-phy.txt b/Documentation/devicetree/bindings/phy/rockchip-pcie-phy.txt new file mode 100644 index 000000000000..0f6222a672ce --- /dev/null +++ b/Documentation/devicetree/bindings/phy/rockchip-pcie-phy.txt @@ -0,0 +1,31 @@ +Rockchip PCIE PHY +----------------------- + +Required properties: + - compatible: rockchip,rk3399-pcie-phy + - #phy-cells: must be 0 + - clocks: Must contain an entry in clock-names. + See ../clocks/clock-bindings.txt for details. + - clock-names: Must be "refclk" + - resets: Must contain an entry in reset-names. + See ../reset/reset.txt for details. + - reset-names: Must be "phy" + +Example: + +grf: syscon@ff770000 { + compatible = "rockchip,rk3399-grf", "syscon", "simple-mfd"; + #address-cells = <1>; + #size-cells = <1>; + + ... + + pcie_phy: pcie-phy { + compatible = "rockchip,rk3399-pcie-phy"; + #phy-cells = <0>; + clocks = <&cru SCLK_PCIEPHY_REF>; + clock-names = "refclk"; + resets = <&cru SRST_PCIEPHY>; + reset-names = "phy"; + }; +}; diff --git a/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt b/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt index cc6be9680a6d..57dc388e2fa2 100644 --- a/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt +++ b/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt @@ -27,6 +27,9 @@ Optional Properties: - clocks : phandle + clock specifier for the phy clocks - clock-names: string, clock name, must be "phyclk" - #clock-cells: for users of the phy-pll, should be 0 +- reset-names: Only allow the following entries: + - phy-reset +- resets: Must contain an entry for each entry in reset-names. Example: diff --git a/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt b/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt index 95736d77fbb7..287150db6db4 100644 --- a/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt +++ b/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt @@ -10,6 +10,7 @@ Required properties: * allwinner,sun8i-a23-usb-phy * allwinner,sun8i-a33-usb-phy * allwinner,sun8i-h3-usb-phy + * allwinner,sun50i-a64-usb-phy - reg : a list of offset + length pairs - reg-names : * "phy_ctrl" diff --git a/Documentation/devicetree/bindings/phy/ti-phy.txt b/Documentation/devicetree/bindings/phy/ti-phy.txt index a3b394587874..cd13e6157088 100644 --- a/Documentation/devicetree/bindings/phy/ti-phy.txt +++ b/Documentation/devicetree/bindings/phy/ti-phy.txt @@ -31,6 +31,8 @@ OMAP USB2 PHY Required properties: - compatible: Should be "ti,omap-usb2" + Should be "ti,dra7x-usb2" for the 1st instance of USB2 PHY on + DRA7x Should be "ti,dra7x-usb2-phy2" for the 2nd instance of USB2 PHY in DRA7x - reg : Address and length of the register set for the device. diff --git a/drivers/extcon/extcon-gpio.c b/drivers/extcon/extcon-gpio.c index e7aebbc945f6..ebed22f22d75 100644 --- a/drivers/extcon/extcon-gpio.c +++ b/drivers/extcon/extcon-gpio.c @@ -49,6 +49,7 @@ static void gpio_extcon_work(struct work_struct *work) state = gpiod_get_value_cansleep(data->id_gpiod); if (data->pdata->gpio_active_low) state = !state; + extcon_set_state_sync(data->edev, data->pdata->extcon_id, state); } diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 19bff3a10f69..fe00f9134d51 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -24,6 +24,15 @@ config PHY_BCM_NS_USB2 Enable this to support Broadcom USB 2.0 PHY connected to the USB controller on Northstar family. +config PHY_BCM_NS_USB3 + tristate "Broadcom Northstar USB 3.0 PHY Driver" + depends on ARCH_BCM_IPROC || COMPILE_TEST + depends on HAS_IOMEM && OF + select GENERIC_PHY + help + Enable this to support Broadcom USB 3.0 PHY connected to the USB + controller on Northstar family. + config PHY_BERLIN_USB tristate "Marvell Berlin USB PHY Driver" depends on ARCH_BERLIN && RESET_CONTROLLER && HAS_IOMEM && OF @@ -258,7 +267,9 @@ config PHY_SUN4I_USB depends on RESET_CONTROLLER depends on EXTCON depends on POWER_SUPPLY + depends on USB_SUPPORT select GENERIC_PHY + select USB_COMMON help Enable this to support the transceiver that is part of Allwinner sunxi SoCs. @@ -358,6 +369,14 @@ config PHY_ROCKCHIP_USB help Enable this to support the Rockchip USB 2.0 PHY. +config PHY_ROCKCHIP_INNO_USB2 + tristate "Rockchip INNO USB2PHY Driver" + depends on (ARCH_ROCKCHIP || COMPILE_TEST) && OF + depends on COMMON_CLK + select GENERIC_PHY + help + Support for Rockchip USB2.0 PHY with Innosilicon IP block. + config PHY_ROCKCHIP_EMMC tristate "Rockchip EMMC PHY Driver" depends on ARCH_ROCKCHIP && OF @@ -372,6 +391,23 @@ config PHY_ROCKCHIP_DP help Enable this to support the Rockchip Display Port PHY. +config PHY_ROCKCHIP_PCIE + tristate "Rockchip PCIe PHY Driver" + depends on (ARCH_ROCKCHIP && OF) || COMPILE_TEST + select GENERIC_PHY + select MFD_SYSCON + help + Enable this to support the Rockchip PCIe PHY. + +config PHY_ROCKCHIP_TYPEC + tristate "Rockchip TYPEC PHY Driver" + depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST) + select EXTCON + select GENERIC_PHY + select RESET_CONTROLLER + help + Enable this to support the Rockchip USB TYPEC PHY. + config PHY_ST_SPEAR1310_MIPHY tristate "ST SPEAR1310-MIPHY driver" select GENERIC_PHY diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 90ae19879b0a..a534cf5be07d 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_GENERIC_PHY) += phy-core.o obj-$(CONFIG_PHY_BCM_NS_USB2) += phy-bcm-ns-usb2.o +obj-$(CONFIG_PHY_BCM_NS_USB3) += phy-bcm-ns-usb3.o obj-$(CONFIG_PHY_BERLIN_USB) += phy-berlin-usb.o obj-$(CONFIG_PHY_BERLIN_SATA) += phy-berlin-sata.o obj-$(CONFIG_PHY_DA8XX_USB) += phy-da8xx-usb.o @@ -39,8 +40,11 @@ phy-exynos-usb2-$(CONFIG_PHY_S5PV210_USB2) += phy-s5pv210-usb2.o obj-$(CONFIG_PHY_EXYNOS5_USBDRD) += phy-exynos5-usbdrd.o obj-$(CONFIG_PHY_QCOM_APQ8064_SATA) += phy-qcom-apq8064-sata.o obj-$(CONFIG_PHY_ROCKCHIP_USB) += phy-rockchip-usb.o +obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o +obj-$(CONFIG_PHY_ROCKCHIP_PCIE) += phy-rockchip-pcie.o obj-$(CONFIG_PHY_ROCKCHIP_DP) += phy-rockchip-dp.o +obj-$(CONFIG_PHY_ROCKCHIP_TYPEC) += phy-rockchip-typec.o obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o obj-$(CONFIG_PHY_ST_SPEAR1310_MIPHY) += phy-spear1310-miphy.o obj-$(CONFIG_PHY_ST_SPEAR1340_MIPHY) += phy-spear1340-miphy.o diff --git a/drivers/phy/phy-bcm-ns-usb3.c b/drivers/phy/phy-bcm-ns-usb3.c new file mode 100644 index 000000000000..f420fa4bebfc --- /dev/null +++ b/drivers/phy/phy-bcm-ns-usb3.c @@ -0,0 +1,274 @@ +/* + * Broadcom Northstar USB 3.0 PHY Driver + * + * Copyright (C) 2016 Rafał Miłecki <rafal@milecki.pl> + * + * All magic values used for initialization (and related comments) were obtained + * from Broadcom's SDK: + * Copyright (c) Broadcom Corp, 2012 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/bcma/bcma.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/phy/phy.h> +#include <linux/slab.h> + +#define BCM_NS_USB3_MII_MNG_TIMEOUT_US 1000 /* usecs */ + +enum bcm_ns_family { + BCM_NS_UNKNOWN, + BCM_NS_AX, + BCM_NS_BX, +}; + +struct bcm_ns_usb3 { + struct device *dev; + enum bcm_ns_family family; + void __iomem *dmp; + void __iomem *ccb_mii; + struct phy *phy; +}; + +static const struct of_device_id bcm_ns_usb3_id_table[] = { + { + .compatible = "brcm,ns-ax-usb3-phy", + .data = (int *)BCM_NS_AX, + }, + { + .compatible = "brcm,ns-bx-usb3-phy", + .data = (int *)BCM_NS_BX, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, bcm_ns_usb3_id_table); + +static int bcm_ns_usb3_wait_reg(struct bcm_ns_usb3 *usb3, void __iomem *addr, + u32 mask, u32 value, unsigned long timeout) +{ + unsigned long deadline = jiffies + timeout; + u32 val; + + do { + val = readl(addr); + if ((val & mask) == value) + return 0; + cpu_relax(); + udelay(10); + } while (!time_after_eq(jiffies, deadline)); + + dev_err(usb3->dev, "Timeout waiting for register %p\n", addr); + + return -EBUSY; +} + +static inline int bcm_ns_usb3_mii_mng_wait_idle(struct bcm_ns_usb3 *usb3) +{ + return bcm_ns_usb3_wait_reg(usb3, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL, + 0x0100, 0x0000, + usecs_to_jiffies(BCM_NS_USB3_MII_MNG_TIMEOUT_US)); +} + +static int bcm_ns_usb3_mii_mng_write32(struct bcm_ns_usb3 *usb3, u32 value) +{ + int err; + + err = bcm_ns_usb3_mii_mng_wait_idle(usb3); + if (err < 0) { + dev_err(usb3->dev, "Couldn't write 0x%08x value\n", value); + return err; + } + + writel(value, usb3->ccb_mii + BCMA_CCB_MII_MNG_CMD_DATA); + + return 0; +} + +static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3) +{ + int err; + + /* Enable MDIO. Setting MDCDIV as 26 */ + writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL); + + /* Wait for MDIO? */ + udelay(2); + + /* USB3 PLL Block */ + err = bcm_ns_usb3_mii_mng_write32(usb3, 0x587e8000); + if (err < 0) + return err; + + /* Assert Ana_Pllseq start */ + bcm_ns_usb3_mii_mng_write32(usb3, 0x58061000); + + /* Assert CML Divider ratio to 26 */ + bcm_ns_usb3_mii_mng_write32(usb3, 0x582a6400); + + /* Asserting PLL Reset */ + bcm_ns_usb3_mii_mng_write32(usb3, 0x582ec000); + + /* Deaaserting PLL Reset */ + bcm_ns_usb3_mii_mng_write32(usb3, 0x582e8000); + + /* Waiting MII Mgt interface idle */ + bcm_ns_usb3_mii_mng_wait_idle(usb3); + + /* Deasserting USB3 system reset */ + writel(0, usb3->dmp + BCMA_RESET_CTL); + + /* PLL frequency monitor enable */ + bcm_ns_usb3_mii_mng_write32(usb3, 0x58069000); + + /* PIPE Block */ + bcm_ns_usb3_mii_mng_write32(usb3, 0x587e8060); + + /* CMPMAX & CMPMINTH setting */ + bcm_ns_usb3_mii_mng_write32(usb3, 0x580af30d); + + /* DEGLITCH MIN & MAX setting */ + bcm_ns_usb3_mii_mng_write32(usb3, 0x580e6302); + + /* TXPMD block */ + bcm_ns_usb3_mii_mng_write32(usb3, 0x587e8040); + + /* Enabling SSC */ + bcm_ns_usb3_mii_mng_write32(usb3, 0x58061003); + + /* Waiting MII Mgt interface idle */ + bcm_ns_usb3_mii_mng_wait_idle(usb3); + + return 0; +} + +static int bcm_ns_usb3_phy_init_ns_ax(struct bcm_ns_usb3 *usb3) +{ + int err; + + /* Enable MDIO. Setting MDCDIV as 26 */ + writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL); + + /* Wait for MDIO? */ + udelay(2); + + /* PLL30 block */ + err = bcm_ns_usb3_mii_mng_write32(usb3, 0x587e8000); + if (err < 0) + return err; + + bcm_ns_usb3_mii_mng_write32(usb3, 0x582a6400); + + bcm_ns_usb3_mii_mng_write32(usb3, 0x587e80e0); + + bcm_ns_usb3_mii_mng_write32(usb3, 0x580a009c); + + /* Enable SSC */ + bcm_ns_usb3_mii_mng_write32(usb3, 0x587e8040); + + bcm_ns_usb3_mii_mng_write32(usb3, 0x580a21d3); + + bcm_ns_usb3_mii_mng_write32(usb3, 0x58061003); + + /* Waiting MII Mgt interface idle */ + bcm_ns_usb3_mii_mng_wait_idle(usb3); + + /* Deasserting USB3 system reset */ + writel(0, usb3->dmp + BCMA_RESET_CTL); + + return 0; +} + +static int bcm_ns_usb3_phy_init(struct phy *phy) +{ + struct bcm_ns_usb3 *usb3 = phy_get_drvdata(phy); + int err; + + /* Perform USB3 system soft reset */ + writel(BCMA_RESET_CTL_RESET, usb3->dmp + BCMA_RESET_CTL); + + switch (usb3->family) { + case BCM_NS_AX: + err = bcm_ns_usb3_phy_init_ns_ax(usb3); + break; + case BCM_NS_BX: + err = bcm_ns_usb3_phy_init_ns_bx(usb3); + break; + default: + WARN_ON(1); + err = -ENOTSUPP; + } + + return err; +} + +static const struct phy_ops ops = { + .init = bcm_ns_usb3_phy_init, + .owner = THIS_MODULE, +}; + +static int bcm_ns_usb3_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct of_device_id *of_id; + struct bcm_ns_usb3 *usb3; + struct resource *res; + struct phy_provider *phy_provider; + + usb3 = devm_kzalloc(dev, sizeof(*usb3), GFP_KERNEL); + if (!usb3) + return -ENOMEM; + + usb3->dev = dev; + + of_id = of_match_device(bcm_ns_usb3_id_table, dev); + if (!of_id) + return -EINVAL; + usb3->family = (enum bcm_ns_family)of_id->data; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dmp"); + usb3->dmp = devm_ioremap_resource(dev, res); + if (IS_ERR(usb3->dmp)) { + dev_err(dev, "Failed to map DMP regs\n"); + return PTR_ERR(usb3->dmp); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ccb-mii"); + usb3->ccb_mii = devm_ioremap_resource(dev, res); + if (IS_ERR(usb3->ccb_mii)) { + dev_err(dev, "Failed to map ChipCommon B MII regs\n"); + return PTR_ERR(usb3->ccb_mii); + } + + usb3->phy = devm_phy_create(dev, NULL, &ops); + if (IS_ERR(usb3->phy)) { + dev_err(dev, "Failed to create PHY\n"); + return PTR_ERR(usb3->phy); + } + + phy_set_drvdata(usb3->phy, usb3); + platform_set_drvdata(pdev, usb3); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (!IS_ERR(phy_provider)) + dev_info(dev, "Registered Broadcom Northstar USB 3.0 PHY driver\n"); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct platform_driver bcm_ns_usb3_driver = { + .probe = bcm_ns_usb3_probe, + .driver = { + .name = "bcm_ns_usb3", + .of_match_table = bcm_ns_usb3_id_table, + }, +}; +module_platform_driver(bcm_ns_usb3_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-bcm-ns2-pcie.c b/drivers/phy/phy-bcm-ns2-pcie.c index 9513f7ab1eaa..4c7d11d2b378 100644 --- a/drivers/phy/phy-bcm-ns2-pcie.c +++ b/drivers/phy/phy-bcm-ns2-pcie.c @@ -18,11 +18,6 @@ #include <linux/phy.h> #include <linux/phy/phy.h> -struct ns2_pci_phy { - struct mdio_device *mdiodev; - struct phy *phy; -}; - #define BLK_ADDR_REG_OFFSET 0x1f #define PLL_AFE1_100MHZ_BLK 0x2100 #define PLL_CLK_AMP_OFFSET 0x03 @@ -30,17 +25,17 @@ struct ns2_pci_phy { static int ns2_pci_phy_init(struct phy *p) { - struct ns2_pci_phy *phy = phy_get_drvdata(p); + struct mdio_device *mdiodev = phy_get_drvdata(p); int rc; /* select the AFE 100MHz block page */ - rc = mdiobus_write(phy->mdiodev->bus, phy->mdiodev->addr, + rc = mdiobus_write(mdiodev->bus, mdiodev->addr, BLK_ADDR_REG_OFFSET, PLL_AFE1_100MHZ_BLK); if (rc) goto err; /* set the 100 MHz reference clock amplitude to 2.05 v */ - rc = mdiobus_write(phy->mdiodev->bus, phy->mdiodev->addr, + rc = mdiobus_write(mdiodev->bus, mdiodev->addr, PLL_CLK_AMP_OFFSET, PLL_CLK_AMP_2P05V); if (rc) goto err; @@ -48,19 +43,19 @@ static int ns2_pci_phy_init(struct phy *p) return 0; err: - dev_err(&phy->mdiodev->dev, "Error %d writing to phy\n", rc); + dev_err(&mdiodev->dev, "Error %d writing to phy\n", rc); return rc; } -static struct phy_ops ns2_pci_phy_ops = { +static const struct phy_ops ns2_pci_phy_ops = { .init = ns2_pci_phy_init, + .owner = THIS_MODULE, }; static int ns2_pci_phy_probe(struct mdio_device *mdiodev) { struct device *dev = &mdiodev->dev; struct phy_provider *provider; - struct ns2_pci_phy *p; struct phy *phy; phy = devm_phy_create(dev, dev->of_node, &ns2_pci_phy_ops); @@ -69,16 +64,7 @@ static int ns2_pci_phy_probe(struct mdio_device *mdiodev) return PTR_ERR(phy); } - p = devm_kmalloc(dev, sizeof(struct ns2_pci_phy), - GFP_KERNEL); - if (!p) - return -ENOMEM; - - p->mdiodev = mdiodev; - dev_set_drvdata(dev, p); - - p->phy = phy; - phy_set_drvdata(phy, p); + phy_set_drvdata(phy, mdiodev); provider = devm_of_phy_provider_register(&phy->dev, of_phy_simple_xlate); diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index 8eca906b6e70..a268f4d6f3e9 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -357,6 +357,21 @@ int phy_set_mode(struct phy *phy, enum phy_mode mode) } EXPORT_SYMBOL_GPL(phy_set_mode); +int phy_reset(struct phy *phy) +{ + int ret; + + if (!phy || !phy->ops->reset) + return 0; + + mutex_lock(&phy->mutex); + ret = phy->ops->reset(phy); + mutex_unlock(&phy->mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phy_reset); + /** * _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/phy-da8xx-usb.c b/drivers/phy/phy-da8xx-usb.c index b2e59b6170ac..32ae78c8ca17 100644 --- a/drivers/phy/phy-da8xx-usb.c +++ b/drivers/phy/phy-da8xx-usb.c @@ -154,7 +154,7 @@ static int da8xx_usb_phy_probe(struct platform_device *pdev) d_phy->regmap = syscon_regmap_lookup_by_compatible( "ti,da830-cfgchip"); else - d_phy->regmap = syscon_regmap_lookup_by_pdevname("syscon.0"); + d_phy->regmap = syscon_regmap_lookup_by_pdevname("syscon"); if (IS_ERR(d_phy->regmap)) { dev_err(dev, "Failed to get syscon\n"); return PTR_ERR(d_phy->regmap); diff --git a/drivers/phy/phy-exynos5-usbdrd.c b/drivers/phy/phy-exynos5-usbdrd.c index 20696f53303f..07ed608905ac 100644 --- a/drivers/phy/phy-exynos5-usbdrd.c +++ b/drivers/phy/phy-exynos5-usbdrd.c @@ -249,7 +249,7 @@ static void exynos5_usbdrd_phy_isol(struct phy_usb_instance *inst, static unsigned int exynos5_usbdrd_pipe3_set_refclk(struct phy_usb_instance *inst) { - static u32 reg; + u32 reg; struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); /* restore any previous reference clock settings */ @@ -295,7 +295,7 @@ exynos5_usbdrd_pipe3_set_refclk(struct phy_usb_instance *inst) static unsigned int exynos5_usbdrd_utmi_set_refclk(struct phy_usb_instance *inst) { - static u32 reg; + u32 reg; struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); /* restore any previous reference clock settings */ diff --git a/drivers/phy/phy-omap-usb2.c b/drivers/phy/phy-omap-usb2.c index c134989052f5..fe909fd8144f 100644 --- a/drivers/phy/phy-omap-usb2.c +++ b/drivers/phy/phy-omap-usb2.c @@ -133,11 +133,49 @@ static int omap_usb_power_on(struct phy *x) return omap_usb_phy_power(phy, true); } +static int omap_usb2_disable_clocks(struct omap_usb *phy) +{ + clk_disable(phy->wkupclk); + if (!IS_ERR(phy->optclk)) + clk_disable(phy->optclk); + + return 0; +} + +static int omap_usb2_enable_clocks(struct omap_usb *phy) +{ + int ret; + + ret = clk_enable(phy->wkupclk); + if (ret < 0) { + dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret); + goto err0; + } + + if (!IS_ERR(phy->optclk)) { + ret = clk_enable(phy->optclk); + if (ret < 0) { + dev_err(phy->dev, "Failed to enable optclk %d\n", ret); + goto err1; + } + } + + return 0; + +err1: + clk_disable(phy->wkupclk); + +err0: + return ret; +} + static int omap_usb_init(struct phy *x) { struct omap_usb *phy = phy_get_drvdata(x); u32 val; + omap_usb2_enable_clocks(phy); + if (phy->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) { /* * @@ -155,8 +193,16 @@ static int omap_usb_init(struct phy *x) return 0; } +static int omap_usb_exit(struct phy *x) +{ + struct omap_usb *phy = phy_get_drvdata(x); + + return omap_usb2_disable_clocks(phy); +} + static const struct phy_ops ops = { .init = omap_usb_init, + .exit = omap_usb_exit, .power_on = omap_usb_power_on, .power_off = omap_usb_power_off, .owner = THIS_MODULE, @@ -376,65 +422,11 @@ static int omap_usb2_remove(struct platform_device *pdev) return 0; } -#ifdef CONFIG_PM - -static int omap_usb2_runtime_suspend(struct device *dev) -{ - struct platform_device *pdev = to_platform_device(dev); - struct omap_usb *phy = platform_get_drvdata(pdev); - - clk_disable(phy->wkupclk); - if (!IS_ERR(phy->optclk)) - clk_disable(phy->optclk); - - return 0; -} - -static int omap_usb2_runtime_resume(struct device *dev) -{ - struct platform_device *pdev = to_platform_device(dev); - struct omap_usb *phy = platform_get_drvdata(pdev); - int ret; - - ret = clk_enable(phy->wkupclk); - if (ret < 0) { - dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret); - goto err0; - } - - if (!IS_ERR(phy->optclk)) { - ret = clk_enable(phy->optclk); - if (ret < 0) { - dev_err(phy->dev, "Failed to enable optclk %d\n", ret); - goto err1; - } - } - - return 0; - -err1: - clk_disable(phy->wkupclk); - -err0: - return ret; -} - -static const struct dev_pm_ops omap_usb2_pm_ops = { - SET_RUNTIME_PM_OPS(omap_usb2_runtime_suspend, omap_usb2_runtime_resume, - NULL) -}; - -#define DEV_PM_OPS (&omap_usb2_pm_ops) -#else -#define DEV_PM_OPS NULL -#endif - static struct platform_driver omap_usb2_driver = { .probe = omap_usb2_probe, .remove = omap_usb2_remove, .driver = { .name = "omap-usb2", - .pm = DEV_PM_OPS, .of_match_table = omap_usb2_id_table, }, }; diff --git a/drivers/phy/phy-qcom-ufs.c b/drivers/phy/phy-qcom-ufs.c index 107cb57c3513..18a5b495ad65 100644 --- a/drivers/phy/phy-qcom-ufs.c +++ b/drivers/phy/phy-qcom-ufs.c @@ -283,10 +283,8 @@ static int __ufs_qcom_phy_init_vreg(struct phy *phy, err = 0; } snprintf(prop_name, MAX_PROP_NAME, "%s-always-on", name); - if (of_get_property(dev->of_node, prop_name, NULL)) - vreg->is_always_on = true; - else - vreg->is_always_on = false; + vreg->is_always_on = of_property_read_bool(dev->of_node, + prop_name); } if (!strcmp(name, "vdda-pll")) { diff --git a/drivers/phy/phy-rcar-gen3-usb2.c b/drivers/phy/phy-rcar-gen3-usb2.c index 31156c9c4707..3d97eadd247d 100644 --- a/drivers/phy/phy-rcar-gen3-usb2.c +++ b/drivers/phy/phy-rcar-gen3-usb2.c @@ -280,6 +280,7 @@ 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" }, { } }; diff --git a/drivers/phy/phy-rockchip-inno-usb2.c b/drivers/phy/phy-rockchip-inno-usb2.c new file mode 100644 index 000000000000..ac203107b071 --- /dev/null +++ b/drivers/phy/phy-rockchip-inno-usb2.c @@ -0,0 +1,707 @@ +/* + * Rockchip USB2.0 PHY with Innosilicon IP block driver + * + * Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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/clk-provider.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/gpio/consumer.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> + +#define BIT_WRITEABLE_SHIFT 16 +#define SCHEDULE_DELAY (60 * HZ) + +enum rockchip_usb2phy_port_id { + USB2PHY_PORT_OTG, + USB2PHY_PORT_HOST, + USB2PHY_NUM_PORTS, +}; + +enum rockchip_usb2phy_host_state { + PHY_STATE_HS_ONLINE = 0, + PHY_STATE_DISCONNECT = 1, + PHY_STATE_CONNECT = 2, + PHY_STATE_FS_LS_ONLINE = 4, +}; + +struct usb2phy_reg { + unsigned int offset; + unsigned int bitend; + unsigned int bitstart; + unsigned int disable; + unsigned int enable; +}; + +/** + * struct rockchip_usb2phy_port_cfg: usb-phy port configuration. + * @phy_sus: phy suspend register. + * @ls_det_en: linestate detection enable register. + * @ls_det_st: linestate detection state register. + * @ls_det_clr: linestate detection clear register. + * @utmi_ls: utmi linestate state register. + * @utmi_hstdet: utmi host disconnect register. + */ +struct rockchip_usb2phy_port_cfg { + struct usb2phy_reg phy_sus; + struct usb2phy_reg ls_det_en; + struct usb2phy_reg ls_det_st; + struct usb2phy_reg ls_det_clr; + struct usb2phy_reg utmi_ls; + struct usb2phy_reg utmi_hstdet; +}; + +/** + * struct rockchip_usb2phy_cfg: usb-phy configuration. + * @reg: the address offset of grf for usb-phy config. + * @num_ports: specify how many ports that the phy has. + * @clkout_ctl: keep on/turn off output clk of phy. + */ +struct rockchip_usb2phy_cfg { + unsigned int reg; + unsigned int num_ports; + struct usb2phy_reg clkout_ctl; + const struct rockchip_usb2phy_port_cfg port_cfgs[USB2PHY_NUM_PORTS]; +}; + +/** + * struct rockchip_usb2phy_port: usb-phy port data. + * @port_id: flag for otg port or host port. + * @suspended: phy suspended flag. + * @ls_irq: IRQ number assigned for linestate detection. + * @mutex: for register updating in sm_work. + * @sm_work: OTG state machine work. + * @phy_cfg: port register configuration, assigned by driver data. + */ +struct rockchip_usb2phy_port { + struct phy *phy; + unsigned int port_id; + bool suspended; + int ls_irq; + struct mutex mutex; + struct delayed_work sm_work; + const struct rockchip_usb2phy_port_cfg *port_cfg; +}; + +/** + * struct rockchip_usb2phy: usb2.0 phy driver data. + * @grf: General Register Files regmap. + * @clk: clock struct of phy input clk. + * @clk480m: clock struct of phy output clk. + * @clk_hw: clock struct of phy output clk management. + * @phy_cfg: phy register configuration, assigned by driver data. + * @ports: phy port instance. + */ +struct rockchip_usb2phy { + struct device *dev; + struct regmap *grf; + struct clk *clk; + struct clk *clk480m; + struct clk_hw clk480m_hw; + const struct rockchip_usb2phy_cfg *phy_cfg; + struct rockchip_usb2phy_port ports[USB2PHY_NUM_PORTS]; +}; + +static inline int property_enable(struct rockchip_usb2phy *rphy, + const struct usb2phy_reg *reg, bool en) +{ + unsigned int val, mask, tmp; + + tmp = en ? reg->enable : reg->disable; + mask = GENMASK(reg->bitend, reg->bitstart); + val = (tmp << reg->bitstart) | (mask << BIT_WRITEABLE_SHIFT); + + return regmap_write(rphy->grf, reg->offset, val); +} + +static inline bool property_enabled(struct rockchip_usb2phy *rphy, + const struct usb2phy_reg *reg) +{ + int ret; + unsigned int tmp, orig; + unsigned int mask = GENMASK(reg->bitend, reg->bitstart); + + ret = regmap_read(rphy->grf, reg->offset, &orig); + if (ret) + return false; + + tmp = (orig & mask) >> reg->bitstart; + return tmp == reg->enable; +} + +static int rockchip_usb2phy_clk480m_enable(struct clk_hw *hw) +{ + struct rockchip_usb2phy *rphy = + container_of(hw, struct rockchip_usb2phy, clk480m_hw); + int ret; + + /* turn on 480m clk output if it is off */ + if (!property_enabled(rphy, &rphy->phy_cfg->clkout_ctl)) { + ret = property_enable(rphy, &rphy->phy_cfg->clkout_ctl, true); + if (ret) + return ret; + + /* waitting for the clk become stable */ + mdelay(1); + } + + return 0; +} + +static void rockchip_usb2phy_clk480m_disable(struct clk_hw *hw) +{ + struct rockchip_usb2phy *rphy = + container_of(hw, struct rockchip_usb2phy, clk480m_hw); + + /* turn off 480m clk output */ + property_enable(rphy, &rphy->phy_cfg->clkout_ctl, false); +} + +static int rockchip_usb2phy_clk480m_enabled(struct clk_hw *hw) +{ + struct rockchip_usb2phy *rphy = + container_of(hw, struct rockchip_usb2phy, clk480m_hw); + + return property_enabled(rphy, &rphy->phy_cfg->clkout_ctl); +} + +static unsigned long +rockchip_usb2phy_clk480m_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return 480000000; +} + +static const struct clk_ops rockchip_usb2phy_clkout_ops = { + .enable = rockchip_usb2phy_clk480m_enable, + .disable = rockchip_usb2phy_clk480m_disable, + .is_enabled = rockchip_usb2phy_clk480m_enabled, + .recalc_rate = rockchip_usb2phy_clk480m_recalc_rate, +}; + +static void rockchip_usb2phy_clk480m_unregister(void *data) +{ + struct rockchip_usb2phy *rphy = data; + + of_clk_del_provider(rphy->dev->of_node); + clk_unregister(rphy->clk480m); +} + +static int +rockchip_usb2phy_clk480m_register(struct rockchip_usb2phy *rphy) +{ + struct device_node *node = rphy->dev->of_node; + struct clk_init_data init; + const char *clk_name; + int ret; + + init.flags = 0; + init.name = "clk_usbphy_480m"; + init.ops = &rockchip_usb2phy_clkout_ops; + + /* optional override of the clockname */ + of_property_read_string(node, "clock-output-names", &init.name); + + if (rphy->clk) { + clk_name = __clk_get_name(rphy->clk); + init.parent_names = &clk_name; + init.num_parents = 1; + } else { + init.parent_names = NULL; + init.num_parents = 0; + } + + rphy->clk480m_hw.init = &init; + + /* register the clock */ + rphy->clk480m = clk_register(rphy->dev, &rphy->clk480m_hw); + if (IS_ERR(rphy->clk480m)) { + ret = PTR_ERR(rphy->clk480m); + goto err_ret; + } + + ret = of_clk_add_provider(node, of_clk_src_simple_get, rphy->clk480m); + if (ret < 0) + goto err_clk_provider; + + ret = devm_add_action(rphy->dev, rockchip_usb2phy_clk480m_unregister, + rphy); + if (ret < 0) + goto err_unreg_action; + + return 0; + +err_unreg_action: + of_clk_del_provider(node); +err_clk_provider: + clk_unregister(rphy->clk480m); +err_ret: + return ret; +} + +static int rockchip_usb2phy_init(struct phy *phy) +{ + struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); + struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent); + int ret; + + if (rport->port_id == USB2PHY_PORT_HOST) { + /* clear linestate and enable linestate detect irq */ + mutex_lock(&rport->mutex); + + ret = property_enable(rphy, &rport->port_cfg->ls_det_clr, true); + if (ret) { + mutex_unlock(&rport->mutex); + return ret; + } + + ret = property_enable(rphy, &rport->port_cfg->ls_det_en, true); + if (ret) { + mutex_unlock(&rport->mutex); + return ret; + } + + mutex_unlock(&rport->mutex); + schedule_delayed_work(&rport->sm_work, SCHEDULE_DELAY); + } + + return 0; +} + +static int rockchip_usb2phy_power_on(struct phy *phy) +{ + struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); + struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent); + int ret; + + dev_dbg(&rport->phy->dev, "port power on\n"); + + if (!rport->suspended) + return 0; + + ret = clk_prepare_enable(rphy->clk480m); + if (ret) + return ret; + + ret = property_enable(rphy, &rport->port_cfg->phy_sus, false); + if (ret) + return ret; + + rport->suspended = false; + return 0; +} + +static int rockchip_usb2phy_power_off(struct phy *phy) +{ + struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); + struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent); + int ret; + + dev_dbg(&rport->phy->dev, "port power off\n"); + + if (rport->suspended) + return 0; + + ret = property_enable(rphy, &rport->port_cfg->phy_sus, true); + if (ret) + return ret; + + rport->suspended = true; + clk_disable_unprepare(rphy->clk480m); + + return 0; +} + +static int rockchip_usb2phy_exit(struct phy *phy) +{ + struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); + + if (rport->port_id == USB2PHY_PORT_HOST) + cancel_delayed_work_sync(&rport->sm_work); + + return 0; +} + +static const struct phy_ops rockchip_usb2phy_ops = { + .init = rockchip_usb2phy_init, + .exit = rockchip_usb2phy_exit, + .power_on = rockchip_usb2phy_power_on, + .power_off = rockchip_usb2phy_power_off, + .owner = THIS_MODULE, +}; + +/* + * The function manage host-phy port state and suspend/resume phy port + * to save power. + * + * we rely on utmi_linestate and utmi_hostdisconnect to identify whether + * devices is disconnect or not. Besides, we do not need care it is FS/LS + * disconnected or HS disconnected, actually, we just only need get the + * device is disconnected at last through rearm the delayed work, + * to suspend the phy port in _PHY_STATE_DISCONNECT_ case. + * + * NOTE: It may invoke *phy_powr_off or *phy_power_on which will invoke + * some clk related APIs, so do not invoke it from interrupt context directly. + */ +static void rockchip_usb2phy_sm_work(struct work_struct *work) +{ + struct rockchip_usb2phy_port *rport = + container_of(work, struct rockchip_usb2phy_port, sm_work.work); + struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); + unsigned int sh = rport->port_cfg->utmi_hstdet.bitend - + rport->port_cfg->utmi_hstdet.bitstart + 1; + unsigned int ul, uhd, state; + unsigned int ul_mask, uhd_mask; + int ret; + + mutex_lock(&rport->mutex); + + ret = regmap_read(rphy->grf, rport->port_cfg->utmi_ls.offset, &ul); + if (ret < 0) + goto next_schedule; + + ret = regmap_read(rphy->grf, rport->port_cfg->utmi_hstdet.offset, + &uhd); + if (ret < 0) + goto next_schedule; + + uhd_mask = GENMASK(rport->port_cfg->utmi_hstdet.bitend, + rport->port_cfg->utmi_hstdet.bitstart); + ul_mask = GENMASK(rport->port_cfg->utmi_ls.bitend, + rport->port_cfg->utmi_ls.bitstart); + + /* stitch on utmi_ls and utmi_hstdet as phy state */ + state = ((uhd & uhd_mask) >> rport->port_cfg->utmi_hstdet.bitstart) | + (((ul & ul_mask) >> rport->port_cfg->utmi_ls.bitstart) << sh); + + switch (state) { + case PHY_STATE_HS_ONLINE: + dev_dbg(&rport->phy->dev, "HS online\n"); + break; + case PHY_STATE_FS_LS_ONLINE: + /* + * For FS/LS device, the online state share with connect state + * from utmi_ls and utmi_hstdet register, so we distinguish + * them via suspended flag. + * + * Plus, there are two cases, one is D- Line pull-up, and D+ + * line pull-down, the state is 4; another is D+ line pull-up, + * and D- line pull-down, the state is 2. + */ + if (!rport->suspended) { + /* D- line pull-up, D+ line pull-down */ + dev_dbg(&rport->phy->dev, "FS/LS online\n"); + break; + } + /* fall through */ + case PHY_STATE_CONNECT: + if (rport->suspended) { + dev_dbg(&rport->phy->dev, "Connected\n"); + rockchip_usb2phy_power_on(rport->phy); + rport->suspended = false; + } else { + /* D+ line pull-up, D- line pull-down */ + dev_dbg(&rport->phy->dev, "FS/LS online\n"); + } + break; + case PHY_STATE_DISCONNECT: + if (!rport->suspended) { + dev_dbg(&rport->phy->dev, "Disconnected\n"); + rockchip_usb2phy_power_off(rport->phy); + rport->suspended = true; + } + + /* + * activate the linestate detection to get the next device + * plug-in irq. + */ + property_enable(rphy, &rport->port_cfg->ls_det_clr, true); + property_enable(rphy, &rport->port_cfg->ls_det_en, true); + + /* + * we don't need to rearm the delayed work when the phy port + * is suspended. + */ + mutex_unlock(&rport->mutex); + return; + default: + dev_dbg(&rport->phy->dev, "unknown phy state\n"); + break; + } + +next_schedule: + mutex_unlock(&rport->mutex); + schedule_delayed_work(&rport->sm_work, SCHEDULE_DELAY); +} + +static irqreturn_t rockchip_usb2phy_linestate_irq(int irq, void *data) +{ + struct rockchip_usb2phy_port *rport = data; + struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); + + if (!property_enabled(rphy, &rport->port_cfg->ls_det_st)) + return IRQ_NONE; + + mutex_lock(&rport->mutex); + + /* disable linestate detect irq and clear its status */ + property_enable(rphy, &rport->port_cfg->ls_det_en, false); + property_enable(rphy, &rport->port_cfg->ls_det_clr, true); + + mutex_unlock(&rport->mutex); + + /* + * In this case for host phy port, a new device is plugged in, + * meanwhile, if the phy port is suspended, we need rearm the work to + * resume it and mange its states; otherwise, we do nothing about that. + */ + if (rport->suspended && rport->port_id == USB2PHY_PORT_HOST) + rockchip_usb2phy_sm_work(&rport->sm_work.work); + + return IRQ_HANDLED; +} + +static int rockchip_usb2phy_host_port_init(struct rockchip_usb2phy *rphy, + struct rockchip_usb2phy_port *rport, + struct device_node *child_np) +{ + int ret; + + rport->port_id = USB2PHY_PORT_HOST; + rport->port_cfg = &rphy->phy_cfg->port_cfgs[USB2PHY_PORT_HOST]; + rport->suspended = true; + + mutex_init(&rport->mutex); + INIT_DELAYED_WORK(&rport->sm_work, rockchip_usb2phy_sm_work); + + rport->ls_irq = of_irq_get_byname(child_np, "linestate"); + if (rport->ls_irq < 0) { + dev_err(rphy->dev, "no linestate irq provided\n"); + return rport->ls_irq; + } + + ret = devm_request_threaded_irq(rphy->dev, rport->ls_irq, NULL, + rockchip_usb2phy_linestate_irq, + IRQF_ONESHOT, + "rockchip_usb2phy", rport); + if (ret) { + dev_err(rphy->dev, "failed to request irq handle\n"); + return ret; + } + + return 0; +} + +static int rockchip_usb2phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *child_np; + struct phy_provider *provider; + struct rockchip_usb2phy *rphy; + const struct rockchip_usb2phy_cfg *phy_cfgs; + const struct of_device_id *match; + unsigned int reg; + int index, ret; + + rphy = devm_kzalloc(dev, sizeof(*rphy), GFP_KERNEL); + if (!rphy) + return -ENOMEM; + + match = of_match_device(dev->driver->of_match_table, dev); + if (!match || !match->data) { + dev_err(dev, "phy configs are not assigned!\n"); + return -EINVAL; + } + + if (!dev->parent || !dev->parent->of_node) + return -EINVAL; + + rphy->grf = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(rphy->grf)) + return PTR_ERR(rphy->grf); + + if (of_property_read_u32(np, "reg", ®)) { + dev_err(dev, "the reg property is not assigned in %s node\n", + np->name); + return -EINVAL; + } + + rphy->dev = dev; + phy_cfgs = match->data; + platform_set_drvdata(pdev, rphy); + + /* find out a proper config which can be matched with dt. */ + index = 0; + while (phy_cfgs[index].reg) { + if (phy_cfgs[index].reg == reg) { + rphy->phy_cfg = &phy_cfgs[index]; + break; + } + + ++index; + } + + if (!rphy->phy_cfg) { + dev_err(dev, "no phy-config can be matched with %s node\n", + np->name); + return -EINVAL; + } + + rphy->clk = of_clk_get_by_name(np, "phyclk"); + if (!IS_ERR(rphy->clk)) { + clk_prepare_enable(rphy->clk); + } else { + dev_info(&pdev->dev, "no phyclk specified\n"); + rphy->clk = NULL; + } + + ret = rockchip_usb2phy_clk480m_register(rphy); + if (ret) { + dev_err(dev, "failed to register 480m output clock\n"); + goto disable_clks; + } + + index = 0; + for_each_available_child_of_node(np, child_np) { + struct rockchip_usb2phy_port *rport = &rphy->ports[index]; + struct phy *phy; + + /* + * This driver aim to support both otg-port and host-port, + * but unfortunately, the otg part is not ready in current, + * so this comments and below codes are interim, which should + * be changed after otg-port is supplied soon. + */ + if (of_node_cmp(child_np->name, "host-port")) + goto next_child; + + phy = devm_phy_create(dev, child_np, &rockchip_usb2phy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create phy\n"); + ret = PTR_ERR(phy); + goto put_child; + } + + rport->phy = phy; + phy_set_drvdata(rport->phy, rport); + + ret = rockchip_usb2phy_host_port_init(rphy, rport, child_np); + if (ret) + goto put_child; + +next_child: + /* to prevent out of boundary */ + if (++index >= rphy->phy_cfg->num_ports) + break; + } + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(provider); + +put_child: + of_node_put(child_np); +disable_clks: + if (rphy->clk) { + clk_disable_unprepare(rphy->clk); + clk_put(rphy->clk); + } + return ret; +} + +static const struct rockchip_usb2phy_cfg rk3366_phy_cfgs[] = { + { + .reg = 0x700, + .num_ports = 2, + .clkout_ctl = { 0x0724, 15, 15, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0x0728, 15, 0, 0, 0x1d1 }, + .ls_det_en = { 0x0680, 4, 4, 0, 1 }, + .ls_det_st = { 0x0690, 4, 4, 0, 1 }, + .ls_det_clr = { 0x06a0, 4, 4, 0, 1 }, + .utmi_ls = { 0x049c, 14, 13, 0, 1 }, + .utmi_hstdet = { 0x049c, 12, 12, 0, 1 } + } + }, + }, + { /* sentinel */ } +}; + +static const struct rockchip_usb2phy_cfg rk3399_phy_cfgs[] = { + { + .reg = 0xe450, + .num_ports = 2, + .clkout_ctl = { 0xe450, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0xe458, 1, 0, 0x2, 0x1 }, + .ls_det_en = { 0xe3c0, 6, 6, 0, 1 }, + .ls_det_st = { 0xe3e0, 6, 6, 0, 1 }, + .ls_det_clr = { 0xe3d0, 6, 6, 0, 1 }, + .utmi_ls = { 0xe2ac, 22, 21, 0, 1 }, + .utmi_hstdet = { 0xe2ac, 23, 23, 0, 1 } + } + }, + }, + { + .reg = 0xe460, + .num_ports = 2, + .clkout_ctl = { 0xe460, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0xe468, 1, 0, 0x2, 0x1 }, + .ls_det_en = { 0xe3c0, 11, 11, 0, 1 }, + .ls_det_st = { 0xe3e0, 11, 11, 0, 1 }, + .ls_det_clr = { 0xe3d0, 11, 11, 0, 1 }, + .utmi_ls = { 0xe2ac, 26, 25, 0, 1 }, + .utmi_hstdet = { 0xe2ac, 27, 27, 0, 1 } + } + }, + }, + { /* sentinel */ } +}; + +static const struct of_device_id rockchip_usb2phy_dt_match[] = { + { .compatible = "rockchip,rk3366-usb2phy", .data = &rk3366_phy_cfgs }, + { .compatible = "rockchip,rk3399-usb2phy", .data = &rk3399_phy_cfgs }, + {} +}; +MODULE_DEVICE_TABLE(of, rockchip_usb2phy_dt_match); + +static struct platform_driver rockchip_usb2phy_driver = { + .probe = rockchip_usb2phy_probe, + .driver = { + .name = "rockchip-usb2phy", + .of_match_table = rockchip_usb2phy_dt_match, + }, +}; +module_platform_driver(rockchip_usb2phy_driver); + +MODULE_AUTHOR("Frank Wang <frank.wang@rock-chips.com>"); +MODULE_DESCRIPTION("Rockchip USB2.0 PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-rockchip-pcie.c b/drivers/phy/phy-rockchip-pcie.c new file mode 100644 index 000000000000..a2b4c6b58aea --- /dev/null +++ b/drivers/phy/phy-rockchip-pcie.c @@ -0,0 +1,357 @@ +/* + * Rockchip PCIe PHY driver + * + * Copyright (C) 2016 Shawn Lin <shawn.lin@rock-chips.com> + * Copyright (C) 2016 ROCKCHIP, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License. + * + * 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/io.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +/* + * The higher 16-bit of this register is used for write protection + * only if BIT(x + 16) set to 1 the BIT(x) can be written. + */ +#define HIWORD_UPDATE(val, mask, shift) \ + ((val) << (shift) | (mask) << ((shift) + 16)) + +#define PHY_MAX_LANE_NUM 4 +#define PHY_CFG_DATA_SHIFT 7 +#define PHY_CFG_ADDR_SHIFT 1 +#define PHY_CFG_DATA_MASK 0xf +#define PHY_CFG_ADDR_MASK 0x3f +#define PHY_CFG_RD_MASK 0x3ff +#define PHY_CFG_WR_ENABLE 1 +#define PHY_CFG_WR_DISABLE 1 +#define PHY_CFG_WR_SHIFT 0 +#define PHY_CFG_WR_MASK 1 +#define PHY_CFG_PLL_LOCK 0x10 +#define PHY_CFG_CLK_TEST 0x10 +#define PHY_CFG_CLK_SCC 0x12 +#define PHY_CFG_SEPE_RATE BIT(3) +#define PHY_CFG_PLL_100M BIT(3) +#define PHY_PLL_LOCKED BIT(9) +#define PHY_PLL_OUTPUT BIT(10) +#define PHY_LANE_A_STATUS 0x30 +#define PHY_LANE_B_STATUS 0x31 +#define PHY_LANE_C_STATUS 0x32 +#define PHY_LANE_D_STATUS 0x33 +#define PHY_LANE_RX_DET_SHIFT 11 +#define PHY_LANE_RX_DET_TH 0x1 +#define PHY_LANE_IDLE_OFF 0x1 +#define PHY_LANE_IDLE_MASK 0x1 +#define PHY_LANE_IDLE_A_SHIFT 3 +#define PHY_LANE_IDLE_B_SHIFT 4 +#define PHY_LANE_IDLE_C_SHIFT 5 +#define PHY_LANE_IDLE_D_SHIFT 6 + +struct rockchip_pcie_data { + unsigned int pcie_conf; + unsigned int pcie_status; + unsigned int pcie_laneoff; +}; + +struct rockchip_pcie_phy { + struct rockchip_pcie_data *phy_data; + struct regmap *reg_base; + struct reset_control *phy_rst; + struct clk *clk_pciephy_ref; +}; + +static inline void phy_wr_cfg(struct rockchip_pcie_phy *rk_phy, + u32 addr, u32 data) +{ + regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, + HIWORD_UPDATE(data, + PHY_CFG_DATA_MASK, + PHY_CFG_DATA_SHIFT) | + HIWORD_UPDATE(addr, + PHY_CFG_ADDR_MASK, + PHY_CFG_ADDR_SHIFT)); + udelay(1); + regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, + HIWORD_UPDATE(PHY_CFG_WR_ENABLE, + PHY_CFG_WR_MASK, + PHY_CFG_WR_SHIFT)); + udelay(1); + regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, + HIWORD_UPDATE(PHY_CFG_WR_DISABLE, + PHY_CFG_WR_MASK, + PHY_CFG_WR_SHIFT)); +} + +static inline u32 phy_rd_cfg(struct rockchip_pcie_phy *rk_phy, + u32 addr) +{ + u32 val; + + regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, + HIWORD_UPDATE(addr, + PHY_CFG_RD_MASK, + PHY_CFG_ADDR_SHIFT)); + regmap_read(rk_phy->reg_base, + rk_phy->phy_data->pcie_status, + &val); + return val; +} + +static int rockchip_pcie_phy_power_off(struct phy *phy) +{ + struct rockchip_pcie_phy *rk_phy = phy_get_drvdata(phy); + int err = 0; + + err = reset_control_assert(rk_phy->phy_rst); + if (err) { + dev_err(&phy->dev, "assert phy_rst err %d\n", err); + return err; + } + + return 0; +} + +static int rockchip_pcie_phy_power_on(struct phy *phy) +{ + struct rockchip_pcie_phy *rk_phy = phy_get_drvdata(phy); + int err = 0; + u32 status; + unsigned long timeout; + + err = reset_control_deassert(rk_phy->phy_rst); + if (err) { + dev_err(&phy->dev, "deassert phy_rst err %d\n", err); + return err; + } + + regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, + HIWORD_UPDATE(PHY_CFG_PLL_LOCK, + PHY_CFG_ADDR_MASK, + PHY_CFG_ADDR_SHIFT)); + + /* + * No documented timeout value for phy operation below, + * so we make it large enough here. And we use loop-break + * method which should not be harmful. + */ + timeout = jiffies + msecs_to_jiffies(1000); + + err = -EINVAL; + while (time_before(jiffies, timeout)) { + regmap_read(rk_phy->reg_base, + rk_phy->phy_data->pcie_status, + &status); + if (status & PHY_PLL_LOCKED) { + dev_dbg(&phy->dev, "pll locked!\n"); + err = 0; + break; + } + msleep(20); + } + + if (err) { + dev_err(&phy->dev, "pll lock timeout!\n"); + goto err_pll_lock; + } + + phy_wr_cfg(rk_phy, PHY_CFG_CLK_TEST, PHY_CFG_SEPE_RATE); + phy_wr_cfg(rk_phy, PHY_CFG_CLK_SCC, PHY_CFG_PLL_100M); + + err = -ETIMEDOUT; + while (time_before(jiffies, timeout)) { + regmap_read(rk_phy->reg_base, + rk_phy->phy_data->pcie_status, + &status); + if (!(status & PHY_PLL_OUTPUT)) { + dev_dbg(&phy->dev, "pll output enable done!\n"); + err = 0; + break; + } + msleep(20); + } + + if (err) { + dev_err(&phy->dev, "pll output enable timeout!\n"); + goto err_pll_lock; + } + + regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, + HIWORD_UPDATE(PHY_CFG_PLL_LOCK, + PHY_CFG_ADDR_MASK, + PHY_CFG_ADDR_SHIFT)); + err = -EINVAL; + while (time_before(jiffies, timeout)) { + regmap_read(rk_phy->reg_base, + rk_phy->phy_data->pcie_status, + &status); + if (status & PHY_PLL_LOCKED) { + dev_dbg(&phy->dev, "pll relocked!\n"); + err = 0; + break; + } + msleep(20); + } + + if (err) { + dev_err(&phy->dev, "pll relock timeout!\n"); + goto err_pll_lock; + } + + return 0; + +err_pll_lock: + reset_control_assert(rk_phy->phy_rst); + return err; +} + +static int rockchip_pcie_phy_init(struct phy *phy) +{ + struct rockchip_pcie_phy *rk_phy = phy_get_drvdata(phy); + int err = 0; + + err = clk_prepare_enable(rk_phy->clk_pciephy_ref); + if (err) { + dev_err(&phy->dev, "Fail to enable pcie ref clock.\n"); + goto err_refclk; + } + + err = reset_control_assert(rk_phy->phy_rst); + if (err) { + dev_err(&phy->dev, "assert phy_rst err %d\n", err); + goto err_reset; + } + + return err; + +err_reset: + clk_disable_unprepare(rk_phy->clk_pciephy_ref); +err_refclk: + return err; +} + +static int rockchip_pcie_phy_exit(struct phy *phy) +{ + struct rockchip_pcie_phy *rk_phy = phy_get_drvdata(phy); + int err = 0; + + clk_disable_unprepare(rk_phy->clk_pciephy_ref); + + err = reset_control_deassert(rk_phy->phy_rst); + if (err) { + dev_err(&phy->dev, "deassert phy_rst err %d\n", err); + goto err_reset; + } + + return err; + +err_reset: + clk_prepare_enable(rk_phy->clk_pciephy_ref); + return err; +} + +static const struct phy_ops ops = { + .init = rockchip_pcie_phy_init, + .exit = rockchip_pcie_phy_exit, + .power_on = rockchip_pcie_phy_power_on, + .power_off = rockchip_pcie_phy_power_off, + .owner = THIS_MODULE, +}; + +static const struct rockchip_pcie_data rk3399_pcie_data = { + .pcie_conf = 0xe220, + .pcie_status = 0xe2a4, + .pcie_laneoff = 0xe214, +}; + +static const struct of_device_id rockchip_pcie_phy_dt_ids[] = { + { + .compatible = "rockchip,rk3399-pcie-phy", + .data = &rk3399_pcie_data, + }, + {} +}; + +MODULE_DEVICE_TABLE(of, rockchip_pcie_phy_dt_ids); + +static int rockchip_pcie_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rockchip_pcie_phy *rk_phy; + struct phy *generic_phy; + struct phy_provider *phy_provider; + struct regmap *grf; + const struct of_device_id *of_id; + + grf = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(grf)) { + dev_err(dev, "Cannot find GRF syscon\n"); + return PTR_ERR(grf); + } + + rk_phy = devm_kzalloc(dev, sizeof(*rk_phy), GFP_KERNEL); + if (!rk_phy) + return -ENOMEM; + + of_id = of_match_device(rockchip_pcie_phy_dt_ids, &pdev->dev); + if (!of_id) + return -EINVAL; + + rk_phy->phy_data = (struct rockchip_pcie_data *)of_id->data; + rk_phy->reg_base = grf; + + rk_phy->phy_rst = devm_reset_control_get(dev, "phy"); + if (IS_ERR(rk_phy->phy_rst)) { + if (PTR_ERR(rk_phy->phy_rst) != -EPROBE_DEFER) + dev_err(dev, + "missing phy property for reset controller\n"); + return PTR_ERR(rk_phy->phy_rst); + } + + rk_phy->clk_pciephy_ref = devm_clk_get(dev, "refclk"); + if (IS_ERR(rk_phy->clk_pciephy_ref)) { + dev_err(dev, "refclk not found.\n"); + return PTR_ERR(rk_phy->clk_pciephy_ref); + } + + generic_phy = devm_phy_create(dev, dev->of_node, &ops); + if (IS_ERR(generic_phy)) { + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(generic_phy); + } + + phy_set_drvdata(generic_phy, rk_phy); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct platform_driver rockchip_pcie_driver = { + .probe = rockchip_pcie_phy_probe, + .driver = { + .name = "rockchip-pcie-phy", + .of_match_table = rockchip_pcie_phy_dt_ids, + }, +}; + +module_platform_driver(rockchip_pcie_driver); + +MODULE_AUTHOR("Shawn Lin <shawn.lin@rock-chips.com>"); +MODULE_DESCRIPTION("Rockchip PCIe PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-rockchip-typec.c b/drivers/phy/phy-rockchip-typec.c new file mode 100644 index 000000000000..7cfb0f8995de --- /dev/null +++ b/drivers/phy/phy-rockchip-typec.c @@ -0,0 +1,1023 @@ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author: Chris Zhong <zyw@rock-chips.com> + * Kever Yang <kever.yang@rock-chips.com> + * + * 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. + * + * The ROCKCHIP Type-C PHY has two PLL clocks. The first PLL clock + * is used for USB3, the second PLL clock is used for DP. This Type-C PHY has + * 3 working modes: USB3 only mode, DP only mode, and USB3+DP mode. + * At USB3 only mode, both PLL clocks need to be initialized, this allows the + * PHY to switch mode between USB3 and USB3+DP, without disconnecting the USB + * device. + * In The DP only mode, only the DP PLL needs to be powered on, and the 4 lanes + * are all used for DP. + * + * This driver gets extcon cable state and property, then decides which mode to + * select: + * + * 1. USB3 only mode: + * EXTCON_USB or EXTCON_USB_HOST state is true, and + * EXTCON_PROP_USB_SS property is true. + * EXTCON_DISP_DP state is false. + * + * 2. DP only mode: + * EXTCON_DISP_DP state is true, and + * EXTCON_PROP_USB_SS property is false. + * If EXTCON_USB_HOST state is true, it is DP + USB2 mode, since the USB2 phy + * is a separate phy, so this case is still DP only mode. + * + * 3. USB3+DP mode: + * EXTCON_USB_HOST and EXTCON_DISP_DP are both true, and + * EXTCON_PROP_USB_SS property is true. + * + * This Type-C PHY driver supports normal and flip orientation. The orientation + * is reported by the EXTCON_PROP_USB_TYPEC_POLARITY property: true is flip + * orientation, false is normal orientation. + * + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/extcon.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#include <linux/mfd/syscon.h> +#include <linux/phy/phy.h> + +#define CMN_SSM_BANDGAP (0x21 << 2) +#define CMN_SSM_BIAS (0x22 << 2) +#define CMN_PLLSM0_PLLEN (0x29 << 2) +#define CMN_PLLSM0_PLLPRE (0x2a << 2) +#define CMN_PLLSM0_PLLVREF (0x2b << 2) +#define CMN_PLLSM0_PLLLOCK (0x2c << 2) +#define CMN_PLLSM1_PLLEN (0x31 << 2) +#define CMN_PLLSM1_PLLPRE (0x32 << 2) +#define CMN_PLLSM1_PLLVREF (0x33 << 2) +#define CMN_PLLSM1_PLLLOCK (0x34 << 2) +#define CMN_PLLSM1_USER_DEF_CTRL (0x37 << 2) +#define CMN_ICAL_OVRD (0xc1 << 2) +#define CMN_PLL0_VCOCAL_OVRD (0x83 << 2) +#define CMN_PLL0_VCOCAL_INIT (0x84 << 2) +#define CMN_PLL0_VCOCAL_ITER (0x85 << 2) +#define CMN_PLL0_LOCK_REFCNT_START (0x90 << 2) +#define CMN_PLL0_LOCK_PLLCNT_START (0x92 << 2) +#define CMN_PLL0_LOCK_PLLCNT_THR (0x93 << 2) +#define CMN_PLL0_INTDIV (0x94 << 2) +#define CMN_PLL0_FRACDIV (0x95 << 2) +#define CMN_PLL0_HIGH_THR (0x96 << 2) +#define CMN_PLL0_DSM_DIAG (0x97 << 2) +#define CMN_PLL0_SS_CTRL1 (0x98 << 2) +#define CMN_PLL0_SS_CTRL2 (0x99 << 2) +#define CMN_PLL1_VCOCAL_START (0xa1 << 2) +#define CMN_PLL1_VCOCAL_OVRD (0xa3 << 2) +#define CMN_PLL1_VCOCAL_INIT (0xa4 << 2) +#define CMN_PLL1_VCOCAL_ITER (0xa5 << 2) +#define CMN_PLL1_LOCK_REFCNT_START (0xb0 << 2) +#define CMN_PLL1_LOCK_PLLCNT_START (0xb2 << 2) +#define CMN_PLL1_LOCK_PLLCNT_THR (0xb3 << 2) +#define CMN_PLL1_INTDIV (0xb4 << 2) +#define CMN_PLL1_FRACDIV (0xb5 << 2) +#define CMN_PLL1_HIGH_THR (0xb6 << 2) +#define CMN_PLL1_DSM_DIAG (0xb7 << 2) +#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_OVRD (0xf1 << 2) +#define CMN_DIAG_PLL0_FBH_OVRD (0x1c0 << 2) +#define CMN_DIAG_PLL0_FBL_OVRD (0x1c1 << 2) +#define CMN_DIAG_PLL0_OVRD (0x1c2 << 2) +#define CMN_DIAG_PLL0_V2I_TUNE (0x1c5 << 2) +#define CMN_DIAG_PLL0_CP_TUNE (0x1c6 << 2) +#define CMN_DIAG_PLL0_LF_PROG (0x1c7 << 2) +#define CMN_DIAG_PLL1_FBH_OVRD (0x1d0 << 2) +#define CMN_DIAG_PLL1_FBL_OVRD (0x1d1 << 2) +#define CMN_DIAG_PLL1_OVRD (0x1d2 << 2) +#define CMN_DIAG_PLL1_V2I_TUNE (0x1d5 << 2) +#define CMN_DIAG_PLL1_CP_TUNE (0x1d6 << 2) +#define CMN_DIAG_PLL1_LF_PROG (0x1d7 << 2) +#define CMN_DIAG_PLL1_PTATIS_TUNE1 (0x1d8 << 2) +#define CMN_DIAG_PLL1_PTATIS_TUNE2 (0x1d9 << 2) +#define CMN_DIAG_PLL1_INCLK_CTRL (0x1da << 2) +#define CMN_DIAG_HSCLK_SEL (0x1e0 << 2) + +#define XCVR_PSM_RCTRL(n) ((0x4001 | ((n) << 9)) << 2) +#define XCVR_PSM_CAL_TMR(n) ((0x4002 | ((n) << 9)) << 2) +#define XCVR_PSM_A0IN_TMR(n) ((0x4003 | ((n) << 9)) << 2) +#define TX_TXCC_CAL_SCLR_MULT(n) ((0x4047 | ((n) << 9)) << 2) +#define TX_TXCC_CPOST_MULT_00(n) ((0x404c | ((n) << 9)) << 2) +#define TX_TXCC_CPOST_MULT_01(n) ((0x404d | ((n) << 9)) << 2) +#define TX_TXCC_CPOST_MULT_10(n) ((0x404e | ((n) << 9)) << 2) +#define TX_TXCC_CPOST_MULT_11(n) ((0x404f | ((n) << 9)) << 2) +#define TX_TXCC_MGNFS_MULT_000(n) ((0x4050 | ((n) << 9)) << 2) +#define TX_TXCC_MGNFS_MULT_001(n) ((0x4051 | ((n) << 9)) << 2) +#define TX_TXCC_MGNFS_MULT_010(n) ((0x4052 | ((n) << 9)) << 2) +#define TX_TXCC_MGNFS_MULT_011(n) ((0x4053 | ((n) << 9)) << 2) +#define TX_TXCC_MGNFS_MULT_100(n) ((0x4054 | ((n) << 9)) << 2) +#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 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) +#define TX_PSC_A0(n) ((0x4100 | ((n) << 9)) << 2) +#define TX_PSC_A1(n) ((0x4101 | ((n) << 9)) << 2) +#define TX_PSC_A2(n) ((0x4102 | ((n) << 9)) << 2) +#define TX_PSC_A3(n) ((0x4103 | ((n) << 9)) << 2) +#define TX_RCVDET_CTRL(n) ((0x4120 | ((n) << 9)) << 2) +#define TX_RCVDET_EN_TMR(n) ((0x4122 | ((n) << 9)) << 2) +#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) +#define TX_ANA_CTRL_REG_1 (0x5020 << 2) +#define TX_ANA_CTRL_REG_2 (0x5021 << 2) +#define TXDA_COEFF_CALC_CTRL (0x5022 << 2) +#define TX_DIG_CTRL_REG_2 (0x5024 << 2) +#define TXDA_CYA_AUXDA_CYA (0x5025 << 2) +#define TX_ANA_CTRL_REG_3 (0x5026 << 2) +#define TX_ANA_CTRL_REG_4 (0x5027 << 2) +#define TX_ANA_CTRL_REG_5 (0x5029 << 2) + +#define RX_PSC_A0(n) ((0x8000 | ((n) << 9)) << 2) +#define RX_PSC_A1(n) ((0x8001 | ((n) << 9)) << 2) +#define RX_PSC_A2(n) ((0x8002 | ((n) << 9)) << 2) +#define RX_PSC_A3(n) ((0x8003 | ((n) << 9)) << 2) +#define RX_PSC_CAL(n) ((0x8006 | ((n) << 9)) << 2) +#define RX_PSC_RDY(n) ((0x8007 | ((n) << 9)) << 2) +#define RX_IQPI_ILL_CAL_OVRD (0x8023 << 2) +#define RX_EPI_ILL_CAL_OVRD (0x8033 << 2) +#define RX_SDCAL0_OVRD (0x8041 << 2) +#define RX_SDCAL1_OVRD (0x8049 << 2) +#define RX_SLC_INIT (0x806d << 2) +#define RX_SLC_RUN (0x806e << 2) +#define RX_CDRLF_CNFG2 (0x8081 << 2) +#define RX_SIGDET_HL_FILT_TMR(n) ((0x8090 | ((n) << 9)) << 2) +#define RX_SLC_IOP0_OVRD (0x8101 << 2) +#define RX_SLC_IOP1_OVRD (0x8105 << 2) +#define RX_SLC_QOP0_OVRD (0x8109 << 2) +#define RX_SLC_QOP1_OVRD (0x810d << 2) +#define RX_SLC_EOP0_OVRD (0x8111 << 2) +#define RX_SLC_EOP1_OVRD (0x8115 << 2) +#define RX_SLC_ION0_OVRD (0x8119 << 2) +#define RX_SLC_ION1_OVRD (0x811d << 2) +#define RX_SLC_QON0_OVRD (0x8121 << 2) +#define RX_SLC_QON1_OVRD (0x8125 << 2) +#define RX_SLC_EON0_OVRD (0x8129 << 2) +#define RX_SLC_EON1_OVRD (0x812d << 2) +#define RX_SLC_IEP0_OVRD (0x8131 << 2) +#define RX_SLC_IEP1_OVRD (0x8135 << 2) +#define RX_SLC_QEP0_OVRD (0x8139 << 2) +#define RX_SLC_QEP1_OVRD (0x813d << 2) +#define RX_SLC_EEP0_OVRD (0x8141 << 2) +#define RX_SLC_EEP1_OVRD (0x8145 << 2) +#define RX_SLC_IEN0_OVRD (0x8149 << 2) +#define RX_SLC_IEN1_OVRD (0x814d << 2) +#define RX_SLC_QEN0_OVRD (0x8151 << 2) +#define RX_SLC_QEN1_OVRD (0x8155 << 2) +#define RX_SLC_EEN0_OVRD (0x8159 << 2) +#define RX_SLC_EEN1_OVRD (0x815d << 2) +#define RX_REE_CTRL_DATA_MASK(n) ((0x81bb | ((n) << 9)) << 2) +#define RX_DIAG_SIGDET_TUNE(n) ((0x81dc | ((n) << 9)) << 2) +#define RX_DIAG_SC2C_DELAY (0x81e1 << 2) + +#define PMA_LANE_CFG (0xc000 << 2) +#define PIPE_CMN_CTRL1 (0xc001 << 2) +#define PIPE_CMN_CTRL2 (0xc002 << 2) +#define PIPE_COM_LOCK_CFG1 (0xc003 << 2) +#define PIPE_COM_LOCK_CFG2 (0xc004 << 2) +#define PIPE_RCV_DET_INH (0xc005 << 2) +#define DP_MODE_CTL (0xc008 << 2) +#define DP_CLK_CTL (0xc009 << 2) +#define STS (0xc00F << 2) +#define PHY_ISO_CMN_CTRL (0xc010 << 2) +#define PHY_DP_TX_CTL (0xc408 << 2) +#define PMA_CMN_CTRL1 (0xc800 << 2) +#define PHY_PMA_ISO_CMN_CTRL (0xc810 << 2) +#define PHY_ISOLATION_CTRL (0xc81f << 2) +#define PHY_PMA_ISO_XCVR_CTRL(n) ((0xcc11 | ((n) << 6)) << 2) +#define PHY_PMA_ISO_LINK_MODE(n) ((0xcc12 | ((n) << 6)) << 2) +#define PHY_PMA_ISO_PWRST_CTRL(n) ((0xcc13 | ((n) << 6)) << 2) +#define PHY_PMA_ISO_TX_DATA_LO(n) ((0xcc14 | ((n) << 6)) << 2) +#define PHY_PMA_ISO_TX_DATA_HI(n) ((0xcc15 | ((n) << 6)) << 2) +#define PHY_PMA_ISO_RX_DATA_LO(n) ((0xcc16 | ((n) << 6)) << 2) +#define PHY_PMA_ISO_RX_DATA_HI(n) ((0xcc17 | ((n) << 6)) << 2) +#define TX_BIST_CTRL(n) ((0x4140 | ((n) << 9)) << 2) +#define TX_BIST_UDDWR(n) ((0x4141 | ((n) << 9)) << 2) + +/* + * Selects which PLL clock will be driven on the analog high speed + * clock 0: PLL 0 div 1 + * clock 1: PLL 1 div 2 + */ +#define CLK_PLL_CONFIG 0X30 +#define CLK_PLL_MASK 0x33 + +#define CMN_READY BIT(0) + +#define DP_PLL_CLOCK_ENABLE BIT(2) +#define DP_PLL_ENABLE BIT(0) +#define DP_PLL_DATA_RATE_RBR ((2 << 12) | (4 << 8)) +#define DP_PLL_DATA_RATE_HBR ((2 << 12) | (4 << 8)) +#define DP_PLL_DATA_RATE_HBR2 ((1 << 12) | (2 << 8)) + +#define DP_MODE_A0 BIT(4) +#define DP_MODE_A2 BIT(6) +#define DP_MODE_ENTER_A0 0xc101 +#define DP_MODE_ENTER_A2 0xc104 + +#define PHY_MODE_SET_TIMEOUT 100000 + +#define PIN_ASSIGN_C_E 0x51d9 +#define PIN_ASSIGN_D_F 0x5100 + +#define MODE_DISCONNECT 0 +#define MODE_UFP_USB BIT(0) +#define MODE_DFP_USB BIT(1) +#define MODE_DFP_DP BIT(2) + +struct usb3phy_reg { + u32 offset; + u32 enable_bit; + u32 write_enable; +}; + +struct rockchip_usb3phy_port_cfg { + struct usb3phy_reg typec_conn_dir; + struct usb3phy_reg usb3tousb2_en; + struct usb3phy_reg external_psm; + struct usb3phy_reg pipe_status; +}; + +struct rockchip_typec_phy { + struct device *dev; + void __iomem *base; + struct extcon_dev *extcon; + struct regmap *grf_regs; + struct clk *clk_core; + struct clk *clk_ref; + struct reset_control *uphy_rst; + struct reset_control *pipe_rst; + struct reset_control *tcphy_rst; + struct rockchip_usb3phy_port_cfg port_cfgs; + /* mutex to protect access to individual PHYs */ + struct mutex lock; + + bool flip; + u8 mode; +}; + +struct phy_reg { + u16 value; + u32 addr; +}; + +struct phy_reg usb3_pll_cfg[] = { + { 0xf0, CMN_PLL0_VCOCAL_INIT }, + { 0x18, CMN_PLL0_VCOCAL_ITER }, + { 0xd0, CMN_PLL0_INTDIV }, + { 0x4a4a, CMN_PLL0_FRACDIV }, + { 0x34, CMN_PLL0_HIGH_THR }, + { 0x1ee, CMN_PLL0_SS_CTRL1 }, + { 0x7f03, CMN_PLL0_SS_CTRL2 }, + { 0x20, CMN_PLL0_DSM_DIAG }, + { 0, CMN_DIAG_PLL0_OVRD }, + { 0, CMN_DIAG_PLL0_FBH_OVRD }, + { 0, CMN_DIAG_PLL0_FBL_OVRD }, + { 0x7, CMN_DIAG_PLL0_V2I_TUNE }, + { 0x45, CMN_DIAG_PLL0_CP_TUNE }, + { 0x8, CMN_DIAG_PLL0_LF_PROG }, +}; + +struct phy_reg dp_pll_cfg[] = { + { 0xf0, CMN_PLL1_VCOCAL_INIT }, + { 0x18, CMN_PLL1_VCOCAL_ITER }, + { 0x30b9, CMN_PLL1_VCOCAL_START }, + { 0x21c, CMN_PLL1_INTDIV }, + { 0, CMN_PLL1_FRACDIV }, + { 0x5, CMN_PLL1_HIGH_THR }, + { 0x35, CMN_PLL1_SS_CTRL1 }, + { 0x7f1e, CMN_PLL1_SS_CTRL2 }, + { 0x20, CMN_PLL1_DSM_DIAG }, + { 0, CMN_PLLSM1_USER_DEF_CTRL }, + { 0, CMN_DIAG_PLL1_OVRD }, + { 0, CMN_DIAG_PLL1_FBH_OVRD }, + { 0, CMN_DIAG_PLL1_FBL_OVRD }, + { 0x6, CMN_DIAG_PLL1_V2I_TUNE }, + { 0x45, CMN_DIAG_PLL1_CP_TUNE }, + { 0x8, CMN_DIAG_PLL1_LF_PROG }, + { 0x100, CMN_DIAG_PLL1_PTATIS_TUNE1 }, + { 0x7, CMN_DIAG_PLL1_PTATIS_TUNE2 }, + { 0x4, CMN_DIAG_PLL1_INCLK_CTRL }, +}; + +static void tcphy_cfg_24m(struct rockchip_typec_phy *tcphy) +{ + u32 i, rdata; + + /* + * cmn_ref_clk_sel = 3, select the 24Mhz for clk parent + * cmn_psm_clk_dig_div = 2, set the clk division to 2 + */ + writel(0x830, tcphy->base + PMA_CMN_CTRL1); + for (i = 0; i < 4; i++) { + /* + * The following PHY configuration assumes a 24 MHz reference + * clock. + */ + writel(0x90, tcphy->base + XCVR_DIAG_LANE_FCM_EN_MGN(i)); + writel(0x960, tcphy->base + TX_RCVDET_EN_TMR(i)); + writel(0x30, tcphy->base + TX_RCVDET_ST_TMR(i)); + } + + rdata = readl(tcphy->base + CMN_DIAG_HSCLK_SEL); + rdata &= ~CLK_PLL_MASK; + rdata |= CLK_PLL_CONFIG; + writel(rdata, tcphy->base + CMN_DIAG_HSCLK_SEL); +} + +static void tcphy_cfg_usb3_pll(struct rockchip_typec_phy *tcphy) +{ + u32 i; + + /* load the configuration of PLL0 */ + for (i = 0; i < ARRAY_SIZE(usb3_pll_cfg); i++) + writel(usb3_pll_cfg[i].value, + tcphy->base + usb3_pll_cfg[i].addr); +} + +static void tcphy_cfg_dp_pll(struct rockchip_typec_phy *tcphy) +{ + u32 i; + + /* set the default mode to RBR */ + writel(DP_PLL_CLOCK_ENABLE | DP_PLL_ENABLE | DP_PLL_DATA_RATE_RBR, + tcphy->base + DP_CLK_CTL); + + /* load the configuration of PLL1 */ + for (i = 0; i < ARRAY_SIZE(dp_pll_cfg); i++) + writel(dp_pll_cfg[i].value, tcphy->base + dp_pll_cfg[i].addr); +} + +static void tcphy_tx_usb3_cfg_lane(struct rockchip_typec_phy *tcphy, u32 lane) +{ + writel(0x7799, tcphy->base + TX_PSC_A0(lane)); + writel(0x7798, tcphy->base + TX_PSC_A1(lane)); + writel(0x5098, tcphy->base + TX_PSC_A2(lane)); + writel(0x5098, tcphy->base + TX_PSC_A3(lane)); + writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_000(lane)); + writel(0xbf, tcphy->base + XCVR_DIAG_BIDI_CTRL(lane)); +} + +static void tcphy_rx_usb3_cfg_lane(struct rockchip_typec_phy *tcphy, u32 lane) +{ + writel(0xa6fd, tcphy->base + RX_PSC_A0(lane)); + writel(0xa6fd, tcphy->base + RX_PSC_A1(lane)); + writel(0xa410, tcphy->base + RX_PSC_A2(lane)); + writel(0x2410, tcphy->base + RX_PSC_A3(lane)); + writel(0x23ff, tcphy->base + RX_PSC_CAL(lane)); + writel(0x13, tcphy->base + RX_SIGDET_HL_FILT_TMR(lane)); + writel(0x03e7, tcphy->base + RX_REE_CTRL_DATA_MASK(lane)); + writel(0x1004, tcphy->base + RX_DIAG_SIGDET_TUNE(lane)); + writel(0x2010, tcphy->base + RX_PSC_RDY(lane)); + writel(0xfb, tcphy->base + XCVR_DIAG_BIDI_CTRL(lane)); +} + +static void tcphy_dp_cfg_lane(struct rockchip_typec_phy *tcphy, u32 lane) +{ + u16 rdata; + + writel(0xbefc, tcphy->base + XCVR_PSM_RCTRL(lane)); + writel(0x6799, tcphy->base + TX_PSC_A0(lane)); + writel(0x6798, tcphy->base + TX_PSC_A1(lane)); + writel(0x98, tcphy->base + TX_PSC_A2(lane)); + writel(0x98, tcphy->base + TX_PSC_A3(lane)); + + writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_000(lane)); + writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_001(lane)); + writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_010(lane)); + writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_011(lane)); + writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_100(lane)); + writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_101(lane)); + writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_110(lane)); + writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_111(lane)); + writel(0, tcphy->base + TX_TXCC_CPOST_MULT_10(lane)); + writel(0, tcphy->base + TX_TXCC_CPOST_MULT_01(lane)); + writel(0, tcphy->base + TX_TXCC_CPOST_MULT_00(lane)); + writel(0, tcphy->base + TX_TXCC_CPOST_MULT_11(lane)); + + writel(0x128, tcphy->base + TX_TXCC_CAL_SCLR_MULT(lane)); + writel(0x400, tcphy->base + TX_DIAG_TX_DRV(lane)); + + rdata = readl(tcphy->base + XCVR_DIAG_PLLDRC_CTRL(lane)); + rdata = (rdata & 0x8fff) | 0x6000; + writel(rdata, tcphy->base + XCVR_DIAG_PLLDRC_CTRL(lane)); +} + +static inline int property_enable(struct rockchip_typec_phy *tcphy, + const struct usb3phy_reg *reg, bool en) +{ + u32 mask = 1 << reg->write_enable; + u32 val = en << reg->enable_bit; + + return regmap_write(tcphy->grf_regs, reg->offset, val | mask); +} + +static void tcphy_dp_aux_calibration(struct rockchip_typec_phy *tcphy) +{ + u16 rdata, rdata2, val; + + /* disable txda_cal_latch_en for rewrite the calibration values */ + rdata = readl(tcphy->base + TX_ANA_CTRL_REG_1); + val = rdata & 0xdfff; + writel(val, tcphy->base + TX_ANA_CTRL_REG_1); + + /* + * 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. + */ + rdata = readl(tcphy->base + TX_DIG_CTRL_REG_2); + rdata = rdata & 0xffc0; + + rdata2 = readl(tcphy->base + CMN_TXPUCAL_CTRL); + rdata2 = rdata2 & 0x3f; + + val = rdata | rdata2; + writel(val, tcphy->base + TX_DIG_CTRL_REG_2); + usleep_range(1000, 1050); + + /* + * Enable signal for latch that sample and holds calibration values. + * Activate this signal for 1 clock cycle to sample new calibration + * values. + */ + rdata = readl(tcphy->base + TX_ANA_CTRL_REG_1); + val = rdata | 0x2000; + writel(val, 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); + writel(0x2008, tcphy->base + TX_ANA_CTRL_REG_1); + writel(0x2018, tcphy->base + TX_ANA_CTRL_REG_1); + + writel(0, tcphy->base + TX_ANA_CTRL_REG_5); + + /* + * Programs txda_drv_ldo_prog[15:0], Sets driver LDO + * voltage 16'h1001 for DP-AUX-TX and RX + */ + writel(0x1001, tcphy->base + TX_ANA_CTRL_REG_4); + + /* re-enables Bandgap reference for LDO */ + writel(0x2098, tcphy->base + TX_ANA_CTRL_REG_1); + writel(0x2198, 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); + + /* + * BIT 12: Controls auxda_polarity, which selects the polarity of the + * xcvr: + * 1, Reverses the polarity (If TYPEC, Pulls ups aux_p and pull + * down aux_m) + * 0, Normal polarity (if TYPE_C, pulls up aux_m and pulls down + * aux_p) + */ + val = 0xa078; + if (!tcphy->flip) + val |= BIT(12); + writel(val, 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, set the voltage swing of the driver + * to 400mv. The values below are peak to peak (differential) values. + */ + writel(4, tcphy->base + TXDA_COEFF_CALC_CTRL); + writel(0, tcphy->base + TXDA_CYA_AUXDA_CYA); + + /* Controls tx_high_z_tm_en */ + val = readl(tcphy->base + TX_DIG_CTRL_REG_2); + val |= BIT(15); + writel(val, tcphy->base + TX_DIG_CTRL_REG_2); +} + +static int tcphy_phy_init(struct rockchip_typec_phy *tcphy, u8 mode) +{ + struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs; + int ret, i; + u32 val; + + ret = clk_prepare_enable(tcphy->clk_core); + if (ret) { + dev_err(tcphy->dev, "Failed to prepare_enable core clock\n"); + return ret; + } + + ret = clk_prepare_enable(tcphy->clk_ref); + if (ret) { + dev_err(tcphy->dev, "Failed to prepare_enable ref clock\n"); + goto err_clk_core; + } + + reset_control_deassert(tcphy->tcphy_rst); + + property_enable(tcphy, &cfg->typec_conn_dir, tcphy->flip); + + tcphy_cfg_24m(tcphy); + + if (mode == MODE_DFP_DP) { + tcphy_cfg_dp_pll(tcphy); + for (i = 0; i < 4; i++) + tcphy_dp_cfg_lane(tcphy, i); + + writel(PIN_ASSIGN_C_E, tcphy->base + PMA_LANE_CFG); + } else { + tcphy_cfg_usb3_pll(tcphy); + tcphy_cfg_dp_pll(tcphy); + if (tcphy->flip) { + tcphy_tx_usb3_cfg_lane(tcphy, 3); + tcphy_rx_usb3_cfg_lane(tcphy, 2); + tcphy_dp_cfg_lane(tcphy, 0); + tcphy_dp_cfg_lane(tcphy, 1); + } else { + tcphy_tx_usb3_cfg_lane(tcphy, 0); + tcphy_rx_usb3_cfg_lane(tcphy, 1); + tcphy_dp_cfg_lane(tcphy, 2); + tcphy_dp_cfg_lane(tcphy, 3); + } + + writel(PIN_ASSIGN_D_F, tcphy->base + PMA_LANE_CFG); + } + + writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL); + + reset_control_deassert(tcphy->uphy_rst); + + ret = readx_poll_timeout(readl, tcphy->base + PMA_CMN_CTRL1, + val, val & CMN_READY, 10, + PHY_MODE_SET_TIMEOUT); + if (ret < 0) { + dev_err(tcphy->dev, "wait pma ready timeout\n"); + ret = -ETIMEDOUT; + goto err_wait_pma; + } + + reset_control_deassert(tcphy->pipe_rst); + + return 0; + +err_wait_pma: + reset_control_assert(tcphy->uphy_rst); + reset_control_assert(tcphy->tcphy_rst); + clk_disable_unprepare(tcphy->clk_ref); +err_clk_core: + clk_disable_unprepare(tcphy->clk_core); + return ret; +} + +static void tcphy_phy_deinit(struct rockchip_typec_phy *tcphy) +{ + reset_control_assert(tcphy->tcphy_rst); + reset_control_assert(tcphy->uphy_rst); + reset_control_assert(tcphy->pipe_rst); + clk_disable_unprepare(tcphy->clk_core); + clk_disable_unprepare(tcphy->clk_ref); +} + +static int tcphy_get_mode(struct rockchip_typec_phy *tcphy) +{ + struct extcon_dev *edev = tcphy->extcon; + union extcon_property_value property; + unsigned int id; + bool dfp, ufp, dp; + u8 mode; + int ret; + + ufp = extcon_get_state(edev, EXTCON_USB); + dfp = extcon_get_state(edev, EXTCON_USB_HOST); + dp = extcon_get_state(edev, EXTCON_DISP_DP); + + mode = MODE_DFP_USB; + id = EXTCON_USB_HOST; + + if (ufp) { + mode = MODE_UFP_USB; + id = EXTCON_USB; + } else if (dp) { + mode = MODE_DFP_DP; + id = EXTCON_DISP_DP; + + ret = extcon_get_property(edev, id, EXTCON_PROP_USB_SS, + &property); + if (ret) { + dev_err(tcphy->dev, "get superspeed property failed\n"); + return ret; + } + + if (property.intval) + mode |= MODE_DFP_USB; + } + + ret = extcon_get_property(edev, id, EXTCON_PROP_USB_TYPEC_POLARITY, + &property); + if (ret) { + dev_err(tcphy->dev, "get polarity property failed\n"); + return ret; + } + + tcphy->flip = property.intval ? 1 : 0; + + return mode; +} + +static int rockchip_usb3_phy_power_on(struct phy *phy) +{ + struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy); + struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs; + const struct usb3phy_reg *reg = &cfg->pipe_status; + int timeout, new_mode, ret = 0; + u32 val; + + mutex_lock(&tcphy->lock); + + new_mode = tcphy_get_mode(tcphy); + if (new_mode < 0) { + ret = new_mode; + goto unlock_ret; + } + + /* DP-only mode; fall back to USB2 */ + if (!(new_mode & (MODE_DFP_USB | MODE_UFP_USB))) + goto unlock_ret; + + if (tcphy->mode == new_mode) + goto unlock_ret; + + if (tcphy->mode == MODE_DISCONNECT) + tcphy_phy_init(tcphy, new_mode); + + /* wait TCPHY for pipe ready */ + for (timeout = 0; timeout < 100; timeout++) { + regmap_read(tcphy->grf_regs, reg->offset, &val); + if (!(val & BIT(reg->enable_bit))) { + tcphy->mode |= new_mode & (MODE_DFP_USB | MODE_UFP_USB); + goto unlock_ret; + } + usleep_range(10, 20); + } + + if (tcphy->mode == MODE_DISCONNECT) + tcphy_phy_deinit(tcphy); + + ret = -ETIMEDOUT; + +unlock_ret: + mutex_unlock(&tcphy->lock); + return ret; +} + +static int rockchip_usb3_phy_power_off(struct phy *phy) +{ + struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy); + + mutex_lock(&tcphy->lock); + + if (tcphy->mode == MODE_DISCONNECT) + goto unlock; + + tcphy->mode &= ~(MODE_UFP_USB | MODE_DFP_USB); + if (tcphy->mode == MODE_DISCONNECT) + tcphy_phy_deinit(tcphy); + +unlock: + mutex_unlock(&tcphy->lock); + return 0; +} + +static const struct phy_ops rockchip_usb3_phy_ops = { + .power_on = rockchip_usb3_phy_power_on, + .power_off = rockchip_usb3_phy_power_off, + .owner = THIS_MODULE, +}; + +static int rockchip_dp_phy_power_on(struct phy *phy) +{ + struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy); + int new_mode, ret = 0; + u32 val; + + mutex_lock(&tcphy->lock); + + new_mode = tcphy_get_mode(tcphy); + if (new_mode < 0) { + ret = new_mode; + goto unlock_ret; + } + + if (!(new_mode & MODE_DFP_DP)) { + ret = -ENODEV; + goto unlock_ret; + } + + if (tcphy->mode == new_mode) + goto unlock_ret; + + /* + * If the PHY has been power on, but the mode is not DP only mode, + * re-init the PHY for setting all of 4 lanes to DP. + */ + if (new_mode == MODE_DFP_DP && tcphy->mode != MODE_DISCONNECT) { + tcphy_phy_deinit(tcphy); + tcphy_phy_init(tcphy, new_mode); + } else if (tcphy->mode == MODE_DISCONNECT) { + tcphy_phy_init(tcphy, new_mode); + } + + ret = readx_poll_timeout(readl, tcphy->base + DP_MODE_CTL, + val, val & DP_MODE_A2, 1000, + PHY_MODE_SET_TIMEOUT); + if (ret < 0) { + dev_err(tcphy->dev, "failed to wait TCPHY enter A2\n"); + goto power_on_finish; + } + + tcphy_dp_aux_calibration(tcphy); + + writel(DP_MODE_ENTER_A0, tcphy->base + DP_MODE_CTL); + + ret = readx_poll_timeout(readl, tcphy->base + DP_MODE_CTL, + val, val & DP_MODE_A0, 1000, + PHY_MODE_SET_TIMEOUT); + if (ret < 0) { + writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL); + dev_err(tcphy->dev, "failed to wait TCPHY enter A0\n"); + goto power_on_finish; + } + + tcphy->mode |= MODE_DFP_DP; + +power_on_finish: + if (tcphy->mode == MODE_DISCONNECT) + tcphy_phy_deinit(tcphy); +unlock_ret: + mutex_unlock(&tcphy->lock); + return ret; +} + +static int rockchip_dp_phy_power_off(struct phy *phy) +{ + struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy); + + mutex_lock(&tcphy->lock); + + if (tcphy->mode == MODE_DISCONNECT) + goto unlock; + + tcphy->mode &= ~MODE_DFP_DP; + + writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL); + + if (tcphy->mode == MODE_DISCONNECT) + tcphy_phy_deinit(tcphy); + +unlock: + mutex_unlock(&tcphy->lock); + return 0; +} + +static const struct phy_ops rockchip_dp_phy_ops = { + .power_on = rockchip_dp_phy_power_on, + .power_off = rockchip_dp_phy_power_off, + .owner = THIS_MODULE, +}; + +static int tcphy_get_param(struct device *dev, + struct usb3phy_reg *reg, + const char *name) +{ + u32 buffer[3]; + int ret; + + ret = of_property_read_u32_array(dev->of_node, name, buffer, 3); + if (ret) { + dev_err(dev, "Can not parse %s\n", name); + return ret; + } + + reg->offset = buffer[0]; + reg->enable_bit = buffer[1]; + reg->write_enable = buffer[2]; + return 0; +} + +static int tcphy_parse_dt(struct rockchip_typec_phy *tcphy, + struct device *dev) +{ + struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs; + int ret; + + ret = tcphy_get_param(dev, &cfg->typec_conn_dir, + "rockchip,typec-conn-dir"); + if (ret) + return ret; + + ret = tcphy_get_param(dev, &cfg->usb3tousb2_en, + "rockchip,usb3tousb2-en"); + if (ret) + return ret; + + ret = tcphy_get_param(dev, &cfg->external_psm, + "rockchip,external-psm"); + if (ret) + return ret; + + ret = tcphy_get_param(dev, &cfg->pipe_status, + "rockchip,pipe-status"); + if (ret) + return ret; + + tcphy->grf_regs = syscon_regmap_lookup_by_phandle(dev->of_node, + "rockchip,grf"); + if (IS_ERR(tcphy->grf_regs)) { + dev_err(dev, "could not find grf dt node\n"); + return PTR_ERR(tcphy->grf_regs); + } + + tcphy->clk_core = devm_clk_get(dev, "tcpdcore"); + if (IS_ERR(tcphy->clk_core)) { + dev_err(dev, "could not get uphy core clock\n"); + return PTR_ERR(tcphy->clk_core); + } + + tcphy->clk_ref = devm_clk_get(dev, "tcpdphy-ref"); + if (IS_ERR(tcphy->clk_ref)) { + dev_err(dev, "could not get uphy ref clock\n"); + return PTR_ERR(tcphy->clk_ref); + } + + tcphy->uphy_rst = devm_reset_control_get(dev, "uphy"); + if (IS_ERR(tcphy->uphy_rst)) { + dev_err(dev, "no uphy_rst reset control found\n"); + return PTR_ERR(tcphy->uphy_rst); + } + + tcphy->pipe_rst = devm_reset_control_get(dev, "uphy-pipe"); + if (IS_ERR(tcphy->pipe_rst)) { + dev_err(dev, "no pipe_rst reset control found\n"); + return PTR_ERR(tcphy->pipe_rst); + } + + tcphy->tcphy_rst = devm_reset_control_get(dev, "uphy-tcphy"); + if (IS_ERR(tcphy->tcphy_rst)) { + dev_err(dev, "no tcphy_rst reset control found\n"); + return PTR_ERR(tcphy->tcphy_rst); + } + + return 0; +} + +static void typec_phy_pre_init(struct rockchip_typec_phy *tcphy) +{ + struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs; + + reset_control_assert(tcphy->tcphy_rst); + reset_control_assert(tcphy->uphy_rst); + reset_control_assert(tcphy->pipe_rst); + + /* select external psm clock */ + property_enable(tcphy, &cfg->external_psm, 1); + property_enable(tcphy, &cfg->usb3tousb2_en, 0); + + tcphy->mode = MODE_DISCONNECT; +} + +static int rockchip_typec_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *child_np; + struct rockchip_typec_phy *tcphy; + struct phy_provider *phy_provider; + struct resource *res; + int ret; + + tcphy = devm_kzalloc(dev, sizeof(*tcphy), GFP_KERNEL); + if (!tcphy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + tcphy->base = devm_ioremap_resource(dev, res); + if (IS_ERR(tcphy->base)) + return PTR_ERR(tcphy->base); + + ret = tcphy_parse_dt(tcphy, dev); + if (ret) + return ret; + + tcphy->dev = dev; + platform_set_drvdata(pdev, tcphy); + mutex_init(&tcphy->lock); + + typec_phy_pre_init(tcphy); + + tcphy->extcon = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(tcphy->extcon)) { + if (PTR_ERR(tcphy->extcon) != -EPROBE_DEFER) + dev_err(dev, "Invalid or missing extcon\n"); + return PTR_ERR(tcphy->extcon); + } + + pm_runtime_enable(dev); + + for_each_available_child_of_node(np, child_np) { + struct phy *phy; + + if (!of_node_cmp(child_np->name, "dp-port")) + phy = devm_phy_create(dev, child_np, + &rockchip_dp_phy_ops); + else if (!of_node_cmp(child_np->name, "usb3-port")) + phy = devm_phy_create(dev, child_np, + &rockchip_usb3_phy_ops); + else + continue; + + if (IS_ERR(phy)) { + dev_err(dev, "failed to create phy: %s\n", + child_np->name); + return PTR_ERR(phy); + } + + phy_set_drvdata(phy, tcphy); + } + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + dev_err(dev, "Failed to register phy provider\n"); + return PTR_ERR(phy_provider); + } + + return 0; +} + +static int rockchip_typec_phy_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct of_device_id rockchip_typec_phy_dt_ids[] = { + { .compatible = "rockchip,rk3399-typec-phy" }, + {} +}; + +MODULE_DEVICE_TABLE(of, rockchip_typec_phy_dt_ids); + +static struct platform_driver rockchip_typec_phy_driver = { + .probe = rockchip_typec_phy_probe, + .remove = rockchip_typec_phy_remove, + .driver = { + .name = "rockchip-typec-phy", + .of_match_table = rockchip_typec_phy_dt_ids, + }, +}; + +module_platform_driver(rockchip_typec_phy_driver); + +MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>"); +MODULE_AUTHOR("Kever Yang <kever.yang@rock-chips.com>"); +MODULE_DESCRIPTION("Rockchip USB TYPE-C PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-rockchip-usb.c b/drivers/phy/phy-rockchip-usb.c index 2a7381f4fe4c..734987fa0ad7 100644 --- a/drivers/phy/phy-rockchip-usb.c +++ b/drivers/phy/phy-rockchip-usb.c @@ -29,6 +29,7 @@ #include <linux/reset.h> #include <linux/regmap.h> #include <linux/mfd/syscon.h> +#include <linux/delay.h> static int enable_usb_uart; @@ -64,6 +65,7 @@ struct rockchip_usb_phy { struct clk_hw clk480m_hw; struct phy *phy; bool uart_enabled; + struct reset_control *reset; }; static int rockchip_usb_phy_power(struct rockchip_usb_phy *phy, @@ -144,9 +146,23 @@ static int rockchip_usb_phy_power_on(struct phy *_phy) return clk_prepare_enable(phy->clk480m); } +static int rockchip_usb_phy_reset(struct phy *_phy) +{ + struct rockchip_usb_phy *phy = phy_get_drvdata(_phy); + + if (phy->reset) { + reset_control_assert(phy->reset); + udelay(10); + reset_control_deassert(phy->reset); + } + + return 0; +} + static const struct phy_ops ops = { .power_on = rockchip_usb_phy_power_on, .power_off = rockchip_usb_phy_power_off, + .reset = rockchip_usb_phy_reset, .owner = THIS_MODULE, }; @@ -185,6 +201,10 @@ static int rockchip_usb_phy_init(struct rockchip_usb_phy_base *base, return -EINVAL; } + rk_phy->reset = of_reset_control_get(child, "phy-reset"); + if (IS_ERR(rk_phy->reset)) + rk_phy->reset = NULL; + rk_phy->reg_offset = reg_offset; rk_phy->clk = of_clk_get_by_name(child, "phyclk"); diff --git a/drivers/phy/phy-sun4i-usb.c b/drivers/phy/phy-sun4i-usb.c index 8c7eb335622e..b9342a2af7b3 100644 --- a/drivers/phy/phy-sun4i-usb.c +++ b/drivers/phy/phy-sun4i-usb.c @@ -40,6 +40,7 @@ #include <linux/power_supply.h> #include <linux/regulator/consumer.h> #include <linux/reset.h> +#include <linux/spinlock.h> #include <linux/usb/of.h> #include <linux/workqueue.h> @@ -50,7 +51,7 @@ #define REG_PHYCTL_A33 0x10 #define REG_PHY_UNK_H3 0x20 -#define REG_PMU_UNK_H3 0x10 +#define REG_PMU_UNK1 0x10 #define PHYCTL_DATA BIT(7) @@ -98,6 +99,7 @@ enum sun4i_usb_phy_type { sun6i_a31_phy, sun8i_a33_phy, sun8i_h3_phy, + sun50i_a64_phy, }; struct sun4i_usb_phy_cfg { @@ -106,13 +108,14 @@ struct sun4i_usb_phy_cfg { u32 disc_thresh; u8 phyctl_offset; bool dedicated_clocks; + bool enable_pmu_unk1; }; struct sun4i_usb_phy_data { void __iomem *base; const struct sun4i_usb_phy_cfg *cfg; enum usb_dr_mode dr_mode; - struct mutex mutex; + spinlock_t reg_lock; /* guard access to phyctl reg */ struct sun4i_usb_phy { struct phy *phy; void __iomem *pmu; @@ -122,7 +125,6 @@ struct sun4i_usb_phy_data { bool regulator_on; int index; } phys[MAX_PHYS]; - int first_phy; /* phy0 / otg related variables */ struct extcon_dev *extcon; bool phy0_init; @@ -131,6 +133,7 @@ struct sun4i_usb_phy_data { struct power_supply *vbus_power_supply; struct notifier_block vbus_power_nb; bool vbus_power_nb_registered; + bool force_session_end; int id_det_irq; int vbus_det_irq; int id_det; @@ -179,12 +182,14 @@ static void sun4i_usb_phy_write(struct sun4i_usb_phy *phy, u32 addr, u32 data, struct sun4i_usb_phy_data *phy_data = to_sun4i_usb_phy_data(phy); u32 temp, usbc_bit = BIT(phy->index * 2); void __iomem *phyctl = phy_data->base + phy_data->cfg->phyctl_offset; + unsigned long flags; int i; - mutex_lock(&phy_data->mutex); + spin_lock_irqsave(&phy_data->reg_lock, flags); - if (phy_data->cfg->type == sun8i_a33_phy) { - /* A33 needs us to set phyctl to 0 explicitly */ + if (phy_data->cfg->type == sun8i_a33_phy || + phy_data->cfg->type == sun50i_a64_phy) { + /* A33 or A64 needs us to set phyctl to 0 explicitly */ writel(0, phyctl); } @@ -218,7 +223,8 @@ static void sun4i_usb_phy_write(struct sun4i_usb_phy *phy, u32 addr, u32 data, data >>= 1; } - mutex_unlock(&phy_data->mutex); + + spin_unlock_irqrestore(&phy_data->reg_lock, flags); } static void sun4i_usb_phy_passby(struct sun4i_usb_phy *phy, int enable) @@ -258,14 +264,16 @@ static int sun4i_usb_phy_init(struct phy *_phy) return ret; } + if (data->cfg->enable_pmu_unk1) { + val = readl(phy->pmu + REG_PMU_UNK1); + writel(val & ~2, phy->pmu + REG_PMU_UNK1); + } + if (data->cfg->type == sun8i_h3_phy) { if (phy->index == 0) { val = readl(data->base + REG_PHY_UNK_H3); writel(val & ~1, data->base + REG_PHY_UNK_H3); } - - val = readl(phy->pmu + REG_PMU_UNK_H3); - writel(val & ~2, phy->pmu + REG_PMU_UNK_H3); } else { /* Enable USB 45 Ohm resistor calibration */ if (phy->index == 0) @@ -320,7 +328,10 @@ static int sun4i_usb_phy0_get_id_det(struct sun4i_usb_phy_data *data) { switch (data->dr_mode) { case USB_DR_MODE_OTG: - return gpiod_get_value_cansleep(data->id_det_gpio); + if (data->id_det_gpio) + return gpiod_get_value_cansleep(data->id_det_gpio); + else + return 1; /* Fallback to peripheral mode */ case USB_DR_MODE_HOST: return 0; case USB_DR_MODE_PERIPHERAL: @@ -382,8 +393,10 @@ static int sun4i_usb_phy_power_on(struct phy *_phy) /* For phy0 only turn on Vbus if we don't have an ext. Vbus */ if (phy->index == 0 && sun4i_usb_phy0_have_vbus_det(data) && - data->vbus_det) + data->vbus_det) { + dev_warn(&_phy->dev, "External vbus detected, not enabling our own vbus\n"); return 0; + } ret = regulator_enable(phy->vbus); if (ret) @@ -419,6 +432,35 @@ static int sun4i_usb_phy_power_off(struct phy *_phy) return 0; } +static int sun4i_usb_phy_set_mode(struct phy *_phy, enum phy_mode mode) +{ + struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); + struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); + + if (phy->index != 0) + return -EINVAL; + + switch (mode) { + case PHY_MODE_USB_HOST: + data->dr_mode = USB_DR_MODE_HOST; + break; + case PHY_MODE_USB_DEVICE: + data->dr_mode = USB_DR_MODE_PERIPHERAL; + break; + case PHY_MODE_USB_OTG: + data->dr_mode = USB_DR_MODE_OTG; + break; + default: + return -EINVAL; + } + + dev_info(&_phy->dev, "Changing dr_mode to %d\n", (int)data->dr_mode); + data->force_session_end = true; + queue_delayed_work(system_wq, &data->detect, 0); + + return 0; +} + void sun4i_usb_phy_set_squelch_detect(struct phy *_phy, bool enabled) { struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); @@ -432,6 +474,7 @@ static const struct phy_ops sun4i_usb_phy_ops = { .exit = sun4i_usb_phy_exit, .power_on = sun4i_usb_phy_power_on, .power_off = sun4i_usb_phy_power_off, + .set_mode = sun4i_usb_phy_set_mode, .owner = THIS_MODULE, }; @@ -440,7 +483,8 @@ static void sun4i_usb_phy0_id_vbus_det_scan(struct work_struct *work) struct sun4i_usb_phy_data *data = container_of(work, struct sun4i_usb_phy_data, detect.work); struct phy *phy0 = data->phys[0].phy; - int id_det, vbus_det, id_notify = 0, vbus_notify = 0; + bool force_session_end, id_notify = false, vbus_notify = false; + int id_det, vbus_det; if (phy0 == NULL) return; @@ -455,27 +499,30 @@ static void sun4i_usb_phy0_id_vbus_det_scan(struct work_struct *work) return; } + force_session_end = data->force_session_end; + data->force_session_end = false; + if (id_det != data->id_det) { - /* - * When a host cable (id == 0) gets plugged in on systems - * without vbus detection report vbus low for long enough for - * the musb-ip to end the current device session. - */ + /* id-change, force session end if we've no vbus detection */ if (data->dr_mode == USB_DR_MODE_OTG && - !sun4i_usb_phy0_have_vbus_det(data) && id_det == 0) { + !sun4i_usb_phy0_have_vbus_det(data)) + force_session_end = true; + + /* When entering host mode (id = 0) force end the session now */ + if (force_session_end && id_det == 0) { sun4i_usb_phy0_set_vbus_detect(phy0, 0); msleep(200); sun4i_usb_phy0_set_vbus_detect(phy0, 1); } sun4i_usb_phy0_set_id_detect(phy0, id_det); data->id_det = id_det; - id_notify = 1; + id_notify = true; } if (vbus_det != data->vbus_det) { sun4i_usb_phy0_set_vbus_detect(phy0, vbus_det); data->vbus_det = vbus_det; - vbus_notify = 1; + vbus_notify = true; } mutex_unlock(&phy0->mutex); @@ -483,13 +530,8 @@ static void sun4i_usb_phy0_id_vbus_det_scan(struct work_struct *work) if (id_notify) { extcon_set_cable_state_(data->extcon, EXTCON_USB_HOST, !id_det); - /* - * When a host cable gets unplugged (id == 1) on systems - * without vbus detection report vbus low for long enough to - * the musb-ip to end the current host session. - */ - if (data->dr_mode == USB_DR_MODE_OTG && - !sun4i_usb_phy0_have_vbus_det(data) && id_det == 1) { + /* When leaving host mode force end the session here */ + if (force_session_end && id_det == 1) { mutex_lock(&phy0->mutex); sun4i_usb_phy0_set_vbus_detect(phy0, 0); msleep(1000); @@ -534,8 +576,7 @@ static struct phy *sun4i_usb_phy_xlate(struct device *dev, { struct sun4i_usb_phy_data *data = dev_get_drvdata(dev); - if (args->args[0] < data->first_phy || - args->args[0] >= data->cfg->num_phys) + if (args->args[0] >= data->cfg->num_phys) return ERR_PTR(-ENODEV); return data->phys[args->args[0]].phy; @@ -577,7 +618,7 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev) if (!data) return -ENOMEM; - mutex_init(&data->mutex); + spin_lock_init(&data->reg_lock); INIT_DELAYED_WORK(&data->detect, sun4i_usb_phy0_id_vbus_det_scan); dev_set_drvdata(dev, data); data->cfg = of_device_get_match_data(dev); @@ -610,33 +651,18 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev) } data->dr_mode = of_usb_get_dr_mode_by_phy(np, 0); - switch (data->dr_mode) { - case USB_DR_MODE_OTG: - /* otg without id_det makes no sense, and is not supported */ - if (!data->id_det_gpio) { - dev_err(dev, "usb0_id_det missing or invalid\n"); - return -ENODEV; - } - /* fall through */ - case USB_DR_MODE_HOST: - case USB_DR_MODE_PERIPHERAL: - data->extcon = devm_extcon_dev_allocate(dev, - sun4i_usb_phy0_cable); - if (IS_ERR(data->extcon)) - return PTR_ERR(data->extcon); - ret = devm_extcon_dev_register(dev, data->extcon); - if (ret) { - dev_err(dev, "failed to register extcon: %d\n", ret); - return ret; - } - break; - default: - dev_info(dev, "dr_mode unknown, not registering usb phy0\n"); - data->first_phy = 1; + data->extcon = devm_extcon_dev_allocate(dev, sun4i_usb_phy0_cable); + if (IS_ERR(data->extcon)) + return PTR_ERR(data->extcon); + + ret = devm_extcon_dev_register(dev, data->extcon); + if (ret) { + dev_err(dev, "failed to register extcon: %d\n", ret); + return ret; } - for (i = data->first_phy; i < data->cfg->num_phys; i++) { + for (i = 0; i < data->cfg->num_phys; i++) { struct sun4i_usb_phy *phy = data->phys + i; char name[16]; @@ -737,6 +763,7 @@ static const struct sun4i_usb_phy_cfg sun4i_a10_cfg = { .disc_thresh = 3, .phyctl_offset = REG_PHYCTL_A10, .dedicated_clocks = false, + .enable_pmu_unk1 = false, }; static const struct sun4i_usb_phy_cfg sun5i_a13_cfg = { @@ -745,6 +772,7 @@ static const struct sun4i_usb_phy_cfg sun5i_a13_cfg = { .disc_thresh = 2, .phyctl_offset = REG_PHYCTL_A10, .dedicated_clocks = false, + .enable_pmu_unk1 = false, }; static const struct sun4i_usb_phy_cfg sun6i_a31_cfg = { @@ -753,6 +781,7 @@ static const struct sun4i_usb_phy_cfg sun6i_a31_cfg = { .disc_thresh = 3, .phyctl_offset = REG_PHYCTL_A10, .dedicated_clocks = true, + .enable_pmu_unk1 = false, }; static const struct sun4i_usb_phy_cfg sun7i_a20_cfg = { @@ -761,6 +790,7 @@ static const struct sun4i_usb_phy_cfg sun7i_a20_cfg = { .disc_thresh = 2, .phyctl_offset = REG_PHYCTL_A10, .dedicated_clocks = false, + .enable_pmu_unk1 = false, }; static const struct sun4i_usb_phy_cfg sun8i_a23_cfg = { @@ -769,6 +799,7 @@ static const struct sun4i_usb_phy_cfg sun8i_a23_cfg = { .disc_thresh = 3, .phyctl_offset = REG_PHYCTL_A10, .dedicated_clocks = true, + .enable_pmu_unk1 = false, }; static const struct sun4i_usb_phy_cfg sun8i_a33_cfg = { @@ -777,6 +808,7 @@ static const struct sun4i_usb_phy_cfg sun8i_a33_cfg = { .disc_thresh = 3, .phyctl_offset = REG_PHYCTL_A33, .dedicated_clocks = true, + .enable_pmu_unk1 = false, }; static const struct sun4i_usb_phy_cfg sun8i_h3_cfg = { @@ -784,6 +816,16 @@ static const struct sun4i_usb_phy_cfg sun8i_h3_cfg = { .type = sun8i_h3_phy, .disc_thresh = 3, .dedicated_clocks = true, + .enable_pmu_unk1 = true, +}; + +static const struct sun4i_usb_phy_cfg sun50i_a64_cfg = { + .num_phys = 2, + .type = sun50i_a64_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A33, + .dedicated_clocks = true, + .enable_pmu_unk1 = true, }; static const struct of_device_id sun4i_usb_phy_of_match[] = { @@ -794,6 +836,8 @@ static const struct of_device_id sun4i_usb_phy_of_match[] = { { .compatible = "allwinner,sun8i-a23-usb-phy", .data = &sun8i_a23_cfg }, { .compatible = "allwinner,sun8i-a33-usb-phy", .data = &sun8i_a33_cfg }, { .compatible = "allwinner,sun8i-h3-usb-phy", .data = &sun8i_h3_cfg }, + { .compatible = "allwinner,sun50i-a64-usb-phy", + .data = &sun50i_a64_cfg}, { }, }; MODULE_DEVICE_TABLE(of, sun4i_usb_phy_of_match); diff --git a/drivers/phy/phy-twl4030-usb.c b/drivers/phy/phy-twl4030-usb.c index d9b10a39a2cf..87e6334eab93 100644 --- a/drivers/phy/phy-twl4030-usb.c +++ b/drivers/phy/phy-twl4030-usb.c @@ -172,6 +172,7 @@ struct twl4030_usb { int irq; enum musb_vbus_id_status linkstat; bool vbus_supplied; + bool musb_mailbox_pending; struct delayed_work id_workaround_work; }; @@ -439,6 +440,17 @@ static int __maybe_unused twl4030_usb_runtime_resume(struct device *dev) (PHY_CLK_CTRL_CLOCKGATING_EN | PHY_CLK_CTRL_CLK32K_EN)); + twl4030_i2c_access(twl, 1); + twl4030_usb_set_mode(twl, twl->usb_mode); + if (twl->usb_mode == T2_USB_MODE_ULPI) + twl4030_i2c_access(twl, 0); + /* + * According to the TPS65950 TRM, there has to be at least 50ms + * delay between setting POWER_CTRL_OTG_ENAB and enabling charging + * so wait here so that a fully enabled phy can be expected after + * resume + */ + msleep(50); return 0; } @@ -459,11 +471,6 @@ static int twl4030_phy_power_on(struct phy *phy) dev_dbg(twl->dev, "%s\n", __func__); pm_runtime_get_sync(twl->dev); - twl4030_i2c_access(twl, 1); - twl4030_usb_set_mode(twl, twl->usb_mode); - if (twl->usb_mode == T2_USB_MODE_ULPI) - twl4030_i2c_access(twl, 0); - twl->linkstat = MUSB_UNKNOWN; schedule_delayed_work(&twl->id_workaround_work, HZ); return 0; @@ -569,9 +576,12 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl) pm_runtime_mark_last_busy(twl->dev); pm_runtime_put_autosuspend(twl->dev); } + twl->musb_mailbox_pending = true; + } + if (twl->musb_mailbox_pending) { err = musb_mailbox(status); - if (err) - twl->linkstat = MUSB_UNKNOWN; + if (!err) + twl->musb_mailbox_pending = false; } /* don't schedule during sleep - irq works right then */ @@ -676,6 +686,7 @@ static int twl4030_usb_probe(struct platform_device *pdev) twl->irq = platform_get_irq(pdev, 0); twl->vbus_supplied = false; twl->linkstat = MUSB_UNKNOWN; + twl->musb_mailbox_pending = false; twl->phy.dev = twl->dev; twl->phy.label = "twl4030"; diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index ec83dfdbc206..873424ab0e32 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -18,6 +18,7 @@ #include <linux/of.h> #include <linux/of_device.h> #include <linux/phy/phy.h> +#include <linux/phy/tegra/xusb.h> #include <linux/platform_device.h> #include <linux/regulator/consumer.h> #include <linux/reset.h> @@ -101,7 +102,8 @@ tegra_xusb_pad_find_phy_node(struct tegra_xusb_pad *pad, unsigned int index) return of_find_node_by_name(np, pad->soc->lanes[index].name); } -int tegra_xusb_lane_lookup_function(struct tegra_xusb_lane *lane, +static int +tegra_xusb_lane_lookup_function(struct tegra_xusb_lane *lane, const char *function) { unsigned int i; diff --git a/include/linux/extcon.h b/include/linux/extcon.h index 5c35f9d1822c..2b9f15156115 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -249,7 +249,6 @@ extern int extcon_set_state(struct extcon_dev *edev, unsigned int id, bool cable_state); extern int extcon_set_state_sync(struct extcon_dev *edev, unsigned int id, bool cable_state); - /* * Synchronize the state and property data for a specific external connector. */ @@ -359,10 +358,27 @@ static inline int extcon_set_state_sync(struct extcon_dev *edev, unsigned int id } static inline int extcon_sync(struct extcon_dev *edev, unsigned int id) +<<<<<<< HEAD +======= +{ + return 0; +} + +static inline int extcon_get_property(struct extcon_dev *edev, unsigned int id, + unsigned int prop, + union extcon_property_value *prop_val) +{ + return 0; +} +static inline int extcon_set_property(struct extcon_dev *edev, unsigned int id, + unsigned int prop, + union extcon_property_value prop_val) +>>>>>>> next { return 0; } +<<<<<<< HEAD static inline int extcon_get_property(struct extcon_dev *edev, unsigned int id, unsigned int prop, union extcon_property_value *prop_val) @@ -371,24 +387,36 @@ static inline int extcon_get_property(struct extcon_dev *edev, unsigned int id, } static inline int extcon_set_property(struct extcon_dev *edev, unsigned int id, unsigned int prop, +======= +static inline int extcon_set_property_sync(struct extcon_dev *edev, + unsigned int id, unsigned int prop, +>>>>>>> next union extcon_property_value prop_val) { return 0; } +<<<<<<< HEAD static inline int extcon_set_property_sync(struct extcon_dev *edev, unsigned int id, unsigned int prop, union extcon_property_value prop_val) +======= +static inline int extcon_get_property_capability(struct extcon_dev *edev, + unsigned int id, unsigned int prop) +>>>>>>> next { return 0; } +<<<<<<< HEAD static inline int extcon_get_property_capability(struct extcon_dev *edev, unsigned int id, unsigned int prop) { return 0; } +======= +>>>>>>> next static inline int extcon_set_property_capability(struct extcon_dev *edev, unsigned int id, unsigned int prop) { diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h index f08b67238b58..ee1bed7dbfc6 100644 --- a/include/linux/phy/phy.h +++ b/include/linux/phy/phy.h @@ -36,6 +36,7 @@ enum phy_mode { * @power_on: powering on the phy * @power_off: powering off the phy * @set_mode: set the mode of the phy + * @reset: resetting the phy * @owner: the module owner containing the ops */ struct phy_ops { @@ -44,6 +45,7 @@ struct phy_ops { int (*power_on)(struct phy *phy); int (*power_off)(struct phy *phy); int (*set_mode)(struct phy *phy, enum phy_mode mode); + int (*reset)(struct phy *phy); struct module *owner; }; @@ -136,6 +138,7 @@ int phy_exit(struct phy *phy); 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); static inline int phy_get_bus_width(struct phy *phy) { return phy->attrs.bus_width; |