summaryrefslogtreecommitdiffstats
path: root/drivers/phy
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:02:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:02:30 +0000
commit76cb841cb886eef6b3bee341a2266c76578724ad (patch)
treef5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/phy
parentInitial commit. (diff)
downloadlinux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz
linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip
Adding upstream version 4.19.249.upstream/4.19.249upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/phy')
-rw-r--r--drivers/phy/Kconfig60
-rw-r--r--drivers/phy/Makefile25
-rw-r--r--drivers/phy/allwinner/Kconfig31
-rw-r--r--drivers/phy/allwinner/Makefile2
-rw-r--r--drivers/phy/allwinner/phy-sun4i-usb.c999
-rw-r--r--drivers/phy/allwinner/phy-sun9i-usb.c202
-rw-r--r--drivers/phy/amlogic/Kconfig38
-rw-r--r--drivers/phy/amlogic/Makefile3
-rw-r--r--drivers/phy/amlogic/phy-meson-gxl-usb2.c309
-rw-r--r--drivers/phy/amlogic/phy-meson-gxl-usb3.c282
-rw-r--r--drivers/phy/amlogic/phy-meson8b-usb2.c287
-rw-r--r--drivers/phy/broadcom/Kconfig93
-rw-r--r--drivers/phy/broadcom/Makefile13
-rw-r--r--drivers/phy/broadcom/phy-bcm-cygnus-pcie.c221
-rw-r--r--drivers/phy/broadcom/phy-bcm-kona-usb2.c155
-rw-r--r--drivers/phy/broadcom/phy-bcm-ns-usb2.c137
-rw-r--r--drivers/phy/broadcom/phy-bcm-ns-usb3.c405
-rw-r--r--drivers/phy/broadcom/phy-bcm-ns2-pcie.c101
-rw-r--r--drivers/phy/broadcom/phy-bcm-ns2-usbdrd.c437
-rw-r--r--drivers/phy/broadcom/phy-bcm-sr-pcie.c305
-rw-r--r--drivers/phy/broadcom/phy-brcm-sata.c669
-rw-r--r--drivers/phy/broadcom/phy-brcm-usb-init.c1019
-rw-r--r--drivers/phy/broadcom/phy-brcm-usb-init.h50
-rw-r--r--drivers/phy/broadcom/phy-brcm-usb.c467
-rw-r--r--drivers/phy/hisilicon/Kconfig40
-rw-r--r--drivers/phy/hisilicon/Makefile4
-rw-r--r--drivers/phy/hisilicon/phy-hi6220-usb.c168
-rw-r--r--drivers/phy/hisilicon/phy-hisi-inno-usb2.c197
-rw-r--r--drivers/phy/hisilicon/phy-histb-combphy.c289
-rw-r--r--drivers/phy/hisilicon/phy-hix5hd2-sata.c191
-rw-r--r--drivers/phy/lantiq/Kconfig9
-rw-r--r--drivers/phy/lantiq/Makefile1
-rw-r--r--drivers/phy/lantiq/phy-lantiq-rcu-usb2.c253
-rw-r--r--drivers/phy/marvell/Kconfig61
-rw-r--r--drivers/phy/marvell/Makefile8
-rw-r--r--drivers/phy/marvell/phy-armada375-usb2.c158
-rw-r--r--drivers/phy/marvell/phy-berlin-sata.c296
-rw-r--r--drivers/phy/marvell/phy-berlin-usb.c211
-rw-r--r--drivers/phy/marvell/phy-mvebu-cp110-comphy.c661
-rw-r--r--drivers/phy/marvell/phy-mvebu-sata.c138
-rw-r--r--drivers/phy/marvell/phy-pxa-28nm-hsic.c220
-rw-r--r--drivers/phy/marvell/phy-pxa-28nm-usb2.c355
-rw-r--r--drivers/phy/mediatek/Kconfig23
-rw-r--r--drivers/phy/mediatek/Makefile7
-rw-r--r--drivers/phy/mediatek/phy-mtk-tphy.c1192
-rw-r--r--drivers/phy/mediatek/phy-mtk-xsphy.c600
-rw-r--r--drivers/phy/motorola/Kconfig20
-rw-r--r--drivers/phy/motorola/Makefile6
-rw-r--r--drivers/phy/motorola/phy-cpcap-usb.c698
-rw-r--r--drivers/phy/motorola/phy-mapphone-mdm6600.c648
-rw-r--r--drivers/phy/phy-core.c1061
-rw-r--r--drivers/phy/phy-lpc18xx-usb-otg.c149
-rw-r--r--drivers/phy/phy-pistachio-usb.c206
-rw-r--r--drivers/phy/phy-xgene.c1735
-rw-r--r--drivers/phy/qualcomm/Kconfig67
-rw-r--r--drivers/phy/qualcomm/Makefile11
-rw-r--r--drivers/phy/qualcomm/phy-ath79-usb.c108
-rw-r--r--drivers/phy/qualcomm/phy-qcom-apq8064-sata.c287
-rw-r--r--drivers/phy/qualcomm/phy-qcom-ipq806x-sata.c209
-rw-r--r--drivers/phy/qualcomm/phy-qcom-qmp.c1668
-rw-r--r--drivers/phy/qualcomm/phy-qcom-qmp.h287
-rw-r--r--drivers/phy/qualcomm/phy-qcom-qusb2.c897
-rw-r--r--drivers/phy/qualcomm/phy-qcom-ufs-i.h156
-rw-r--r--drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c203
-rw-r--r--drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.h177
-rw-r--r--drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c257
-rw-r--r--drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.h235
-rw-r--r--drivers/phy/qualcomm/phy-qcom-ufs.c682
-rw-r--r--drivers/phy/qualcomm/phy-qcom-usb-hs.c291
-rw-r--r--drivers/phy/qualcomm/phy-qcom-usb-hsic.c159
-rw-r--r--drivers/phy/ralink/Kconfig12
-rw-r--r--drivers/phy/ralink/Makefile1
-rw-r--r--drivers/phy/ralink/phy-ralink-usb.c249
-rw-r--r--drivers/phy/renesas/Kconfig33
-rw-r--r--drivers/phy/renesas/Makefile4
-rw-r--r--drivers/phy/renesas/phy-rcar-gen2.c339
-rw-r--r--drivers/phy/renesas/phy-rcar-gen3-pcie.c151
-rw-r--r--drivers/phy/renesas/phy-rcar-gen3-usb2.c536
-rw-r--r--drivers/phy/renesas/phy-rcar-gen3-usb3.c226
-rw-r--r--drivers/phy/rockchip/Kconfig52
-rw-r--r--drivers/phy/rockchip/Makefile7
-rw-r--r--drivers/phy/rockchip/phy-rockchip-dp.c154
-rw-r--r--drivers/phy/rockchip/phy-rockchip-emmc.c379
-rw-r--r--drivers/phy/rockchip/phy-rockchip-inno-usb2.c1463
-rw-r--r--drivers/phy/rockchip/phy-rockchip-pcie.c449
-rw-r--r--drivers/phy/rockchip/phy-rockchip-typec.c1239
-rw-r--r--drivers/phy/rockchip/phy-rockchip-usb.c540
-rw-r--r--drivers/phy/samsung/Kconfig95
-rw-r--r--drivers/phy/samsung/Makefile12
-rw-r--r--drivers/phy/samsung/phy-exynos-dp-video.c121
-rw-r--r--drivers/phy/samsung/phy-exynos-mipi-video.c371
-rw-r--r--drivers/phy/samsung/phy-exynos-pcie.c281
-rw-r--r--drivers/phy/samsung/phy-exynos4210-usb2.c260
-rw-r--r--drivers/phy/samsung/phy-exynos4x12-usb2.c378
-rw-r--r--drivers/phy/samsung/phy-exynos5-usbdrd.c968
-rw-r--r--drivers/phy/samsung/phy-exynos5250-sata.c259
-rw-r--r--drivers/phy/samsung/phy-exynos5250-usb2.c401
-rw-r--r--drivers/phy/samsung/phy-s5pv210-usb2.c191
-rw-r--r--drivers/phy/samsung/phy-samsung-usb2.c264
-rw-r--r--drivers/phy/samsung/phy-samsung-usb2.h73
-rw-r--r--drivers/phy/st/Kconfig47
-rw-r--r--drivers/phy/st/Makefile5
-rw-r--r--drivers/phy/st/phy-miphy28lp.c1286
-rw-r--r--drivers/phy/st/phy-spear1310-miphy.c261
-rw-r--r--drivers/phy/st/phy-spear1340-miphy.c294
-rw-r--r--drivers/phy/st/phy-stih407-usb.c180
-rw-r--r--drivers/phy/st/phy-stm32-usbphyc.c460
-rw-r--r--drivers/phy/tegra/Kconfig8
-rw-r--r--drivers/phy/tegra/Makefile6
-rw-r--r--drivers/phy/tegra/xusb-tegra124.c1751
-rw-r--r--drivers/phy/tegra/xusb-tegra210.c2043
-rw-r--r--drivers/phy/tegra/xusb.c1007
-rw-r--r--drivers/phy/tegra/xusb.h421
-rw-r--r--drivers/phy/ti/Kconfig78
-rw-r--r--drivers/phy/ti/Makefile8
-rw-r--r--drivers/phy/ti/phy-da8xx-usb.c253
-rw-r--r--drivers/phy/ti/phy-dm816x-usb.c299
-rw-r--r--drivers/phy/ti/phy-omap-control.c360
-rw-r--r--drivers/phy/ti/phy-omap-usb2.c439
-rw-r--r--drivers/phy/ti/phy-ti-pipe3.c796
-rw-r--r--drivers/phy/ti/phy-tusb1210.c182
-rw-r--r--drivers/phy/ti/phy-twl4030-usb.c868
122 files changed, 42169 insertions, 0 deletions
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
new file mode 100644
index 000000000..5c8d452e3
--- /dev/null
+++ b/drivers/phy/Kconfig
@@ -0,0 +1,60 @@
+#
+# PHY
+#
+
+menu "PHY Subsystem"
+
+config GENERIC_PHY
+ bool "PHY Core"
+ help
+ Generic PHY support.
+
+ This framework is designed to provide a generic interface for PHY
+ devices present in the kernel. This layer will have the generic
+ API by which phy drivers can create PHY using the phy framework and
+ phy users can obtain reference to the PHY. All the users of this
+ framework should select this config.
+
+config PHY_LPC18XX_USB_OTG
+ tristate "NXP LPC18xx/43xx SoC USB OTG PHY driver"
+ depends on OF && (ARCH_LPC18XX || COMPILE_TEST)
+ depends on MFD_SYSCON
+ select GENERIC_PHY
+ help
+ Enable this to support NXP LPC18xx/43xx internal USB OTG PHY.
+
+ This driver is need for USB0 support on LPC18xx/43xx and takes
+ care of enabling and clock setup.
+
+config PHY_PISTACHIO_USB
+ tristate "IMG Pistachio USB2.0 PHY driver"
+ depends on MACH_PISTACHIO
+ select GENERIC_PHY
+ help
+ Enable this to support the USB2.0 PHY on the IMG Pistachio SoC.
+
+config PHY_XGENE
+ tristate "APM X-Gene 15Gbps PHY support"
+ depends on HAS_IOMEM && OF && (ARM64 || COMPILE_TEST)
+ select GENERIC_PHY
+ help
+ This option enables support for APM X-Gene SoC multi-purpose PHY.
+
+source "drivers/phy/allwinner/Kconfig"
+source "drivers/phy/amlogic/Kconfig"
+source "drivers/phy/broadcom/Kconfig"
+source "drivers/phy/hisilicon/Kconfig"
+source "drivers/phy/lantiq/Kconfig"
+source "drivers/phy/marvell/Kconfig"
+source "drivers/phy/mediatek/Kconfig"
+source "drivers/phy/motorola/Kconfig"
+source "drivers/phy/qualcomm/Kconfig"
+source "drivers/phy/ralink/Kconfig"
+source "drivers/phy/renesas/Kconfig"
+source "drivers/phy/rockchip/Kconfig"
+source "drivers/phy/samsung/Kconfig"
+source "drivers/phy/st/Kconfig"
+source "drivers/phy/tegra/Kconfig"
+source "drivers/phy/ti/Kconfig"
+
+endmenu
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
new file mode 100644
index 000000000..84e3bd9c5
--- /dev/null
+++ b/drivers/phy/Makefile
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the phy drivers.
+#
+
+obj-$(CONFIG_GENERIC_PHY) += phy-core.o
+obj-$(CONFIG_PHY_LPC18XX_USB_OTG) += phy-lpc18xx-usb-otg.o
+obj-$(CONFIG_PHY_XGENE) += phy-xgene.o
+obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o
+obj-$(CONFIG_ARCH_SUNXI) += allwinner/
+obj-$(CONFIG_ARCH_MESON) += amlogic/
+obj-$(CONFIG_LANTIQ) += lantiq/
+obj-$(CONFIG_ARCH_MEDIATEK) += mediatek/
+obj-$(CONFIG_ARCH_RENESAS) += renesas/
+obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
+obj-$(CONFIG_ARCH_TEGRA) += tegra/
+obj-y += broadcom/ \
+ hisilicon/ \
+ marvell/ \
+ motorola/ \
+ qualcomm/ \
+ ralink/ \
+ samsung/ \
+ st/ \
+ ti/
diff --git a/drivers/phy/allwinner/Kconfig b/drivers/phy/allwinner/Kconfig
new file mode 100644
index 000000000..cdc1e745b
--- /dev/null
+++ b/drivers/phy/allwinner/Kconfig
@@ -0,0 +1,31 @@
+#
+# Phy drivers for Allwinner platforms
+#
+config PHY_SUN4I_USB
+ tristate "Allwinner sunxi SoC USB PHY driver"
+ depends on ARCH_SUNXI && HAS_IOMEM && OF
+ 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.
+
+ This driver controls the entire USB PHY block, both the USB OTG
+ parts, as well as the 2 regular USB 2 host PHYs.
+
+config PHY_SUN9I_USB
+ tristate "Allwinner sun9i SoC USB PHY driver"
+ depends on ARCH_SUNXI && HAS_IOMEM && OF
+ depends on RESET_CONTROLLER
+ depends on USB_SUPPORT
+ select USB_COMMON
+ select GENERIC_PHY
+ help
+ Enable this to support the transceiver that is part of Allwinner
+ sun9i SoCs.
+
+ This driver controls each individual USB 2 host PHY.
diff --git a/drivers/phy/allwinner/Makefile b/drivers/phy/allwinner/Makefile
new file mode 100644
index 000000000..8605529c0
--- /dev/null
+++ b/drivers/phy/allwinner/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4i-usb.o
+obj-$(CONFIG_PHY_SUN9I_USB) += phy-sun9i-usb.o
diff --git a/drivers/phy/allwinner/phy-sun4i-usb.c b/drivers/phy/allwinner/phy-sun4i-usb.c
new file mode 100644
index 000000000..920f61eff
--- /dev/null
+++ b/drivers/phy/allwinner/phy-sun4i-usb.c
@@ -0,0 +1,999 @@
+/*
+ * Allwinner sun4i USB phy driver
+ *
+ * Copyright (C) 2014-2015 Hans de Goede <hdegoede@redhat.com>
+ *
+ * Based on code from
+ * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
+ *
+ * Modelled after: Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY driver
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ * Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
+ *
+ * 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/delay.h>
+#include <linux/err.h>
+#include <linux/extcon-provider.h>
+#include <linux/io.h>
+#include <linux/interrupt.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_device.h>
+#include <linux/of_gpio.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-sun4i-usb.h>
+#include <linux/platform_device.h>
+#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>
+
+#define REG_ISCR 0x00
+#define REG_PHYCTL_A10 0x04
+#define REG_PHYBIST 0x08
+#define REG_PHYTUNE 0x0c
+#define REG_PHYCTL_A33 0x10
+#define REG_PHY_OTGCTL 0x20
+
+#define REG_PMU_UNK1 0x10
+
+#define PHYCTL_DATA BIT(7)
+
+#define OTGCTL_ROUTE_MUSB BIT(0)
+
+#define SUNXI_AHB_ICHR8_EN BIT(10)
+#define SUNXI_AHB_INCR4_BURST_EN BIT(9)
+#define SUNXI_AHB_INCRX_ALIGN_EN BIT(8)
+#define SUNXI_ULPI_BYPASS_EN BIT(0)
+
+/* ISCR, Interface Status and Control bits */
+#define ISCR_ID_PULLUP_EN (1 << 17)
+#define ISCR_DPDM_PULLUP_EN (1 << 16)
+/* sunxi has the phy id/vbus pins not connected, so we use the force bits */
+#define ISCR_FORCE_ID_MASK (3 << 14)
+#define ISCR_FORCE_ID_LOW (2 << 14)
+#define ISCR_FORCE_ID_HIGH (3 << 14)
+#define ISCR_FORCE_VBUS_MASK (3 << 12)
+#define ISCR_FORCE_VBUS_LOW (2 << 12)
+#define ISCR_FORCE_VBUS_HIGH (3 << 12)
+
+/* Common Control Bits for Both PHYs */
+#define PHY_PLL_BW 0x03
+#define PHY_RES45_CAL_EN 0x0c
+
+/* Private Control Bits for Each PHY */
+#define PHY_TX_AMPLITUDE_TUNE 0x20
+#define PHY_TX_SLEWRATE_TUNE 0x22
+#define PHY_VBUSVALID_TH_SEL 0x25
+#define PHY_PULLUP_RES_SEL 0x27
+#define PHY_OTG_FUNC_EN 0x28
+#define PHY_VBUS_DET_EN 0x29
+#define PHY_DISCON_TH_SEL 0x2a
+#define PHY_SQUELCH_DETECT 0x3c
+
+/* A83T specific control bits for PHY0 */
+#define PHY_CTL_VBUSVLDEXT BIT(5)
+#define PHY_CTL_SIDDQ BIT(3)
+
+/* A83T specific control bits for PHY2 HSIC */
+#define SUNXI_EHCI_HS_FORCE BIT(20)
+#define SUNXI_HSIC_CONNECT_DET BIT(17)
+#define SUNXI_HSIC_CONNECT_INT BIT(16)
+#define SUNXI_HSIC BIT(1)
+
+#define MAX_PHYS 4
+
+/*
+ * Note do not raise the debounce time, we must report Vusb high within 100ms
+ * otherwise we get Vbus errors
+ */
+#define DEBOUNCE_TIME msecs_to_jiffies(50)
+#define POLL_TIME msecs_to_jiffies(250)
+
+enum sun4i_usb_phy_type {
+ sun4i_a10_phy,
+ sun6i_a31_phy,
+ sun8i_a33_phy,
+ sun8i_a83t_phy,
+ sun8i_h3_phy,
+ sun8i_r40_phy,
+ sun8i_v3s_phy,
+ sun50i_a64_phy,
+};
+
+struct sun4i_usb_phy_cfg {
+ int num_phys;
+ int hsic_index;
+ enum sun4i_usb_phy_type type;
+ u32 disc_thresh;
+ u8 phyctl_offset;
+ bool dedicated_clocks;
+ bool enable_pmu_unk1;
+ bool phy0_dual_route;
+ int missing_phys;
+};
+
+struct sun4i_usb_phy_data {
+ void __iomem *base;
+ const struct sun4i_usb_phy_cfg *cfg;
+ enum usb_dr_mode dr_mode;
+ spinlock_t reg_lock; /* guard access to phyctl reg */
+ struct sun4i_usb_phy {
+ struct phy *phy;
+ void __iomem *pmu;
+ struct regulator *vbus;
+ struct reset_control *reset;
+ struct clk *clk;
+ struct clk *clk2;
+ bool regulator_on;
+ int index;
+ } phys[MAX_PHYS];
+ /* phy0 / otg related variables */
+ struct extcon_dev *extcon;
+ bool phy0_init;
+ struct gpio_desc *id_det_gpio;
+ struct gpio_desc *vbus_det_gpio;
+ 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;
+ int vbus_det;
+ struct delayed_work detect;
+};
+
+#define to_sun4i_usb_phy_data(phy) \
+ container_of((phy), struct sun4i_usb_phy_data, phys[(phy)->index])
+
+static void sun4i_usb_phy0_update_iscr(struct phy *_phy, u32 clr, u32 set)
+{
+ struct sun4i_usb_phy *phy = phy_get_drvdata(_phy);
+ struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy);
+ u32 iscr;
+
+ iscr = readl(data->base + REG_ISCR);
+ iscr &= ~clr;
+ iscr |= set;
+ writel(iscr, data->base + REG_ISCR);
+}
+
+static void sun4i_usb_phy0_set_id_detect(struct phy *phy, u32 val)
+{
+ if (val)
+ val = ISCR_FORCE_ID_HIGH;
+ else
+ val = ISCR_FORCE_ID_LOW;
+
+ sun4i_usb_phy0_update_iscr(phy, ISCR_FORCE_ID_MASK, val);
+}
+
+static void sun4i_usb_phy0_set_vbus_detect(struct phy *phy, u32 val)
+{
+ if (val)
+ val = ISCR_FORCE_VBUS_HIGH;
+ else
+ val = ISCR_FORCE_VBUS_LOW;
+
+ sun4i_usb_phy0_update_iscr(phy, ISCR_FORCE_VBUS_MASK, val);
+}
+
+static void sun4i_usb_phy_write(struct sun4i_usb_phy *phy, u32 addr, u32 data,
+ int len)
+{
+ 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;
+
+ spin_lock_irqsave(&phy_data->reg_lock, flags);
+
+ if (phy_data->cfg->phyctl_offset == REG_PHYCTL_A33) {
+ /* SoCs newer than A33 need us to set phyctl to 0 explicitly */
+ writel(0, phyctl);
+ }
+
+ for (i = 0; i < len; i++) {
+ temp = readl(phyctl);
+
+ /* clear the address portion */
+ temp &= ~(0xff << 8);
+
+ /* set the address */
+ temp |= ((addr + i) << 8);
+ writel(temp, phyctl);
+
+ /* set the data bit and clear usbc bit*/
+ temp = readb(phyctl);
+ if (data & 0x1)
+ temp |= PHYCTL_DATA;
+ else
+ temp &= ~PHYCTL_DATA;
+ temp &= ~usbc_bit;
+ writeb(temp, phyctl);
+
+ /* pulse usbc_bit */
+ temp = readb(phyctl);
+ temp |= usbc_bit;
+ writeb(temp, phyctl);
+
+ temp = readb(phyctl);
+ temp &= ~usbc_bit;
+ writeb(temp, phyctl);
+
+ data >>= 1;
+ }
+
+ spin_unlock_irqrestore(&phy_data->reg_lock, flags);
+}
+
+static void sun4i_usb_phy_passby(struct sun4i_usb_phy *phy, int enable)
+{
+ struct sun4i_usb_phy_data *phy_data = to_sun4i_usb_phy_data(phy);
+ u32 bits, reg_value;
+
+ if (!phy->pmu)
+ return;
+
+ bits = SUNXI_AHB_ICHR8_EN | SUNXI_AHB_INCR4_BURST_EN |
+ SUNXI_AHB_INCRX_ALIGN_EN | SUNXI_ULPI_BYPASS_EN;
+
+ /* A83T USB2 is HSIC */
+ if (phy_data->cfg->type == sun8i_a83t_phy && phy->index == 2)
+ bits |= SUNXI_EHCI_HS_FORCE | SUNXI_HSIC_CONNECT_INT |
+ SUNXI_HSIC;
+
+ reg_value = readl(phy->pmu);
+
+ if (enable)
+ reg_value |= bits;
+ else
+ reg_value &= ~bits;
+
+ writel(reg_value, phy->pmu);
+}
+
+static int sun4i_usb_phy_init(struct phy *_phy)
+{
+ struct sun4i_usb_phy *phy = phy_get_drvdata(_phy);
+ struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy);
+ int ret;
+ u32 val;
+
+ ret = clk_prepare_enable(phy->clk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(phy->clk2);
+ if (ret) {
+ clk_disable_unprepare(phy->clk);
+ return ret;
+ }
+
+ ret = reset_control_deassert(phy->reset);
+ if (ret) {
+ clk_disable_unprepare(phy->clk2);
+ clk_disable_unprepare(phy->clk);
+ return ret;
+ }
+
+ if (data->cfg->type == sun8i_a83t_phy) {
+ if (phy->index == 0) {
+ val = readl(data->base + data->cfg->phyctl_offset);
+ val |= PHY_CTL_VBUSVLDEXT;
+ val &= ~PHY_CTL_SIDDQ;
+ writel(val, data->base + data->cfg->phyctl_offset);
+ }
+ } else {
+ if (phy->pmu && data->cfg->enable_pmu_unk1) {
+ val = readl(phy->pmu + REG_PMU_UNK1);
+ writel(val & ~2, phy->pmu + REG_PMU_UNK1);
+ }
+
+ /* Enable USB 45 Ohm resistor calibration */
+ if (phy->index == 0)
+ sun4i_usb_phy_write(phy, PHY_RES45_CAL_EN, 0x01, 1);
+
+ /* Adjust PHY's magnitude and rate */
+ sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, 0x14, 5);
+
+ /* Disconnect threshold adjustment */
+ sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL,
+ data->cfg->disc_thresh, 2);
+ }
+
+ sun4i_usb_phy_passby(phy, 1);
+
+ if (phy->index == 0) {
+ data->phy0_init = true;
+
+ /* Enable pull-ups */
+ sun4i_usb_phy0_update_iscr(_phy, 0, ISCR_DPDM_PULLUP_EN);
+ sun4i_usb_phy0_update_iscr(_phy, 0, ISCR_ID_PULLUP_EN);
+
+ /* Force ISCR and cable state updates */
+ data->id_det = -1;
+ data->vbus_det = -1;
+ queue_delayed_work(system_wq, &data->detect, 0);
+ }
+
+ return 0;
+}
+
+static int sun4i_usb_phy_exit(struct phy *_phy)
+{
+ 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) {
+ if (data->cfg->type == sun8i_a83t_phy) {
+ void __iomem *phyctl = data->base +
+ data->cfg->phyctl_offset;
+
+ writel(readl(phyctl) | PHY_CTL_SIDDQ, phyctl);
+ }
+
+ /* Disable pull-ups */
+ sun4i_usb_phy0_update_iscr(_phy, ISCR_DPDM_PULLUP_EN, 0);
+ sun4i_usb_phy0_update_iscr(_phy, ISCR_ID_PULLUP_EN, 0);
+ data->phy0_init = false;
+ }
+
+ sun4i_usb_phy_passby(phy, 0);
+ reset_control_assert(phy->reset);
+ clk_disable_unprepare(phy->clk2);
+ clk_disable_unprepare(phy->clk);
+
+ return 0;
+}
+
+static int sun4i_usb_phy0_get_id_det(struct sun4i_usb_phy_data *data)
+{
+ switch (data->dr_mode) {
+ case USB_DR_MODE_OTG:
+ 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:
+ default:
+ return 1;
+ }
+}
+
+static int sun4i_usb_phy0_get_vbus_det(struct sun4i_usb_phy_data *data)
+{
+ if (data->vbus_det_gpio)
+ return gpiod_get_value_cansleep(data->vbus_det_gpio);
+
+ if (data->vbus_power_supply) {
+ union power_supply_propval val;
+ int r;
+
+ r = power_supply_get_property(data->vbus_power_supply,
+ POWER_SUPPLY_PROP_PRESENT, &val);
+ if (r == 0)
+ return val.intval;
+ }
+
+ /* Fallback: report vbus as high */
+ return 1;
+}
+
+static bool sun4i_usb_phy0_have_vbus_det(struct sun4i_usb_phy_data *data)
+{
+ return data->vbus_det_gpio || data->vbus_power_supply;
+}
+
+static bool sun4i_usb_phy0_poll(struct sun4i_usb_phy_data *data)
+{
+ if ((data->id_det_gpio && data->id_det_irq <= 0) ||
+ (data->vbus_det_gpio && data->vbus_det_irq <= 0))
+ return true;
+
+ /*
+ * The A31/A23/A33 companion pmics (AXP221/AXP223) do not
+ * generate vbus change interrupts when the board is driving
+ * vbus using the N_VBUSEN pin on the pmic, so we must poll
+ * when using the pmic for vbus-det _and_ we're driving vbus.
+ */
+ if ((data->cfg->type == sun6i_a31_phy ||
+ data->cfg->type == sun8i_a33_phy) &&
+ data->vbus_power_supply && data->phys[0].regulator_on)
+ return true;
+
+ return false;
+}
+
+static int sun4i_usb_phy_power_on(struct phy *_phy)
+{
+ struct sun4i_usb_phy *phy = phy_get_drvdata(_phy);
+ struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy);
+ int ret;
+
+ if (!phy->vbus || phy->regulator_on)
+ return 0;
+
+ /* 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) {
+ dev_warn(&_phy->dev, "External vbus detected, not enabling our own vbus\n");
+ return 0;
+ }
+
+ ret = regulator_enable(phy->vbus);
+ if (ret)
+ return ret;
+
+ phy->regulator_on = true;
+
+ /* We must report Vbus high within OTG_TIME_A_WAIT_VRISE msec. */
+ if (phy->index == 0 && sun4i_usb_phy0_poll(data))
+ mod_delayed_work(system_wq, &data->detect, DEBOUNCE_TIME);
+
+ return 0;
+}
+
+static int sun4i_usb_phy_power_off(struct phy *_phy)
+{
+ struct sun4i_usb_phy *phy = phy_get_drvdata(_phy);
+ struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy);
+
+ if (!phy->vbus || !phy->regulator_on)
+ return 0;
+
+ regulator_disable(phy->vbus);
+ phy->regulator_on = false;
+
+ /*
+ * phy0 vbus typically slowly discharges, sometimes this causes the
+ * Vbus gpio to not trigger an edge irq on Vbus off, so force a rescan.
+ */
+ if (phy->index == 0 && !sun4i_usb_phy0_poll(data))
+ mod_delayed_work(system_wq, &data->detect, POLL_TIME);
+
+ 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);
+ int new_mode;
+
+ if (phy->index != 0) {
+ if (mode == PHY_MODE_USB_HOST)
+ return 0;
+ return -EINVAL;
+ }
+
+ switch (mode) {
+ case PHY_MODE_USB_HOST:
+ new_mode = USB_DR_MODE_HOST;
+ break;
+ case PHY_MODE_USB_DEVICE:
+ new_mode = USB_DR_MODE_PERIPHERAL;
+ break;
+ case PHY_MODE_USB_OTG:
+ new_mode = USB_DR_MODE_OTG;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (new_mode != data->dr_mode) {
+ dev_info(&_phy->dev, "Changing dr_mode to %d\n", new_mode);
+ data->dr_mode = new_mode;
+ }
+
+ data->id_det = -1; /* Force reprocessing of id */
+ 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);
+
+ sun4i_usb_phy_write(phy, PHY_SQUELCH_DETECT, enabled ? 0 : 2, 2);
+}
+EXPORT_SYMBOL_GPL(sun4i_usb_phy_set_squelch_detect);
+
+static const struct phy_ops sun4i_usb_phy_ops = {
+ .init = sun4i_usb_phy_init,
+ .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,
+};
+
+static void sun4i_usb_phy0_reroute(struct sun4i_usb_phy_data *data, int id_det)
+{
+ u32 regval;
+
+ regval = readl(data->base + REG_PHY_OTGCTL);
+ if (id_det == 0) {
+ /* Host mode. Route phy0 to EHCI/OHCI */
+ regval &= ~OTGCTL_ROUTE_MUSB;
+ } else {
+ /* Peripheral mode. Route phy0 to MUSB */
+ regval |= OTGCTL_ROUTE_MUSB;
+ }
+ writel(regval, data->base + REG_PHY_OTGCTL);
+}
+
+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;
+ struct sun4i_usb_phy *phy;
+ bool force_session_end, id_notify = false, vbus_notify = false;
+ int id_det, vbus_det;
+
+ if (!phy0)
+ return;
+
+ phy = phy_get_drvdata(phy0);
+ id_det = sun4i_usb_phy0_get_id_det(data);
+ vbus_det = sun4i_usb_phy0_get_vbus_det(data);
+
+ mutex_lock(&phy0->mutex);
+
+ if (!data->phy0_init) {
+ mutex_unlock(&phy0->mutex);
+ return;
+ }
+
+ force_session_end = data->force_session_end;
+ data->force_session_end = false;
+
+ if (id_det != data->id_det) {
+ /* 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))
+ 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 = true;
+ }
+
+ if (vbus_det != data->vbus_det) {
+ sun4i_usb_phy0_set_vbus_detect(phy0, vbus_det);
+ data->vbus_det = vbus_det;
+ vbus_notify = true;
+ }
+
+ mutex_unlock(&phy0->mutex);
+
+ if (id_notify) {
+ extcon_set_state_sync(data->extcon, EXTCON_USB_HOST,
+ !id_det);
+ /* 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);
+ sun4i_usb_phy0_set_vbus_detect(phy0, 1);
+ mutex_unlock(&phy0->mutex);
+ }
+
+ /* Enable PHY0 passby for host mode only. */
+ sun4i_usb_phy_passby(phy, !id_det);
+
+ /* Re-route PHY0 if necessary */
+ if (data->cfg->phy0_dual_route)
+ sun4i_usb_phy0_reroute(data, id_det);
+ }
+
+ if (vbus_notify)
+ extcon_set_state_sync(data->extcon, EXTCON_USB, vbus_det);
+
+ if (sun4i_usb_phy0_poll(data))
+ queue_delayed_work(system_wq, &data->detect, POLL_TIME);
+}
+
+static irqreturn_t sun4i_usb_phy0_id_vbus_det_irq(int irq, void *dev_id)
+{
+ struct sun4i_usb_phy_data *data = dev_id;
+
+ /* vbus or id changed, let the pins settle and then scan them */
+ mod_delayed_work(system_wq, &data->detect, DEBOUNCE_TIME);
+
+ return IRQ_HANDLED;
+}
+
+static int sun4i_usb_phy0_vbus_notify(struct notifier_block *nb,
+ unsigned long val, void *v)
+{
+ struct sun4i_usb_phy_data *data =
+ container_of(nb, struct sun4i_usb_phy_data, vbus_power_nb);
+ struct power_supply *psy = v;
+
+ /* Properties on the vbus_power_supply changed, scan vbus_det */
+ if (val == PSY_EVENT_PROP_CHANGED && psy == data->vbus_power_supply)
+ mod_delayed_work(system_wq, &data->detect, DEBOUNCE_TIME);
+
+ return NOTIFY_OK;
+}
+
+static struct phy *sun4i_usb_phy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct sun4i_usb_phy_data *data = dev_get_drvdata(dev);
+
+ if (args->args[0] >= data->cfg->num_phys)
+ return ERR_PTR(-ENODEV);
+
+ if (data->cfg->missing_phys & BIT(args->args[0]))
+ return ERR_PTR(-ENODEV);
+
+ return data->phys[args->args[0]].phy;
+}
+
+static int sun4i_usb_phy_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sun4i_usb_phy_data *data = dev_get_drvdata(dev);
+
+ if (data->vbus_power_nb_registered)
+ power_supply_unreg_notifier(&data->vbus_power_nb);
+ if (data->id_det_irq > 0)
+ devm_free_irq(dev, data->id_det_irq, data);
+ if (data->vbus_det_irq > 0)
+ devm_free_irq(dev, data->vbus_det_irq, data);
+
+ cancel_delayed_work_sync(&data->detect);
+
+ return 0;
+}
+
+static const unsigned int sun4i_usb_phy0_cable[] = {
+ EXTCON_USB,
+ EXTCON_USB_HOST,
+ EXTCON_NONE,
+};
+
+static int sun4i_usb_phy_probe(struct platform_device *pdev)
+{
+ struct sun4i_usb_phy_data *data;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct phy_provider *phy_provider;
+ struct resource *res;
+ int i, ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ 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);
+ if (!data->cfg)
+ return -EINVAL;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_ctrl");
+ data->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(data->base))
+ return PTR_ERR(data->base);
+
+ data->id_det_gpio = devm_gpiod_get_optional(dev, "usb0_id_det",
+ GPIOD_IN);
+ if (IS_ERR(data->id_det_gpio)) {
+ dev_err(dev, "Couldn't request ID GPIO\n");
+ return PTR_ERR(data->id_det_gpio);
+ }
+
+ data->vbus_det_gpio = devm_gpiod_get_optional(dev, "usb0_vbus_det",
+ GPIOD_IN);
+ if (IS_ERR(data->vbus_det_gpio)) {
+ dev_err(dev, "Couldn't request VBUS detect GPIO\n");
+ return PTR_ERR(data->vbus_det_gpio);
+ }
+
+ if (of_find_property(np, "usb0_vbus_power-supply", NULL)) {
+ data->vbus_power_supply = devm_power_supply_get_by_phandle(dev,
+ "usb0_vbus_power-supply");
+ if (IS_ERR(data->vbus_power_supply)) {
+ dev_err(dev, "Couldn't get the VBUS power supply\n");
+ return PTR_ERR(data->vbus_power_supply);
+ }
+
+ if (!data->vbus_power_supply)
+ return -EPROBE_DEFER;
+ }
+
+ data->dr_mode = of_usb_get_dr_mode_by_phy(np, 0);
+
+ data->extcon = devm_extcon_dev_allocate(dev, sun4i_usb_phy0_cable);
+ if (IS_ERR(data->extcon)) {
+ dev_err(dev, "Couldn't allocate our extcon device\n");
+ 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 = 0; i < data->cfg->num_phys; i++) {
+ struct sun4i_usb_phy *phy = data->phys + i;
+ char name[16];
+
+ if (data->cfg->missing_phys & BIT(i))
+ continue;
+
+ snprintf(name, sizeof(name), "usb%d_vbus", i);
+ phy->vbus = devm_regulator_get_optional(dev, name);
+ if (IS_ERR(phy->vbus)) {
+ if (PTR_ERR(phy->vbus) == -EPROBE_DEFER) {
+ dev_err(dev,
+ "Couldn't get regulator %s... Deferring probe\n",
+ name);
+ return -EPROBE_DEFER;
+ }
+
+ phy->vbus = NULL;
+ }
+
+ if (data->cfg->dedicated_clocks)
+ snprintf(name, sizeof(name), "usb%d_phy", i);
+ else
+ strlcpy(name, "usb_phy", sizeof(name));
+
+ phy->clk = devm_clk_get(dev, name);
+ if (IS_ERR(phy->clk)) {
+ dev_err(dev, "failed to get clock %s\n", name);
+ return PTR_ERR(phy->clk);
+ }
+
+ /* The first PHY is always tied to OTG, and never HSIC */
+ if (data->cfg->hsic_index && i == data->cfg->hsic_index) {
+ /* HSIC needs secondary clock */
+ snprintf(name, sizeof(name), "usb%d_hsic_12M", i);
+ phy->clk2 = devm_clk_get(dev, name);
+ if (IS_ERR(phy->clk2)) {
+ dev_err(dev, "failed to get clock %s\n", name);
+ return PTR_ERR(phy->clk2);
+ }
+ }
+
+ snprintf(name, sizeof(name), "usb%d_reset", i);
+ phy->reset = devm_reset_control_get(dev, name);
+ if (IS_ERR(phy->reset)) {
+ dev_err(dev, "failed to get reset %s\n", name);
+ return PTR_ERR(phy->reset);
+ }
+
+ if (i || data->cfg->phy0_dual_route) { /* No pmu for musb */
+ snprintf(name, sizeof(name), "pmu%d", i);
+ res = platform_get_resource_byname(pdev,
+ IORESOURCE_MEM, name);
+ phy->pmu = devm_ioremap_resource(dev, res);
+ if (IS_ERR(phy->pmu))
+ return PTR_ERR(phy->pmu);
+ }
+
+ phy->phy = devm_phy_create(dev, NULL, &sun4i_usb_phy_ops);
+ if (IS_ERR(phy->phy)) {
+ dev_err(dev, "failed to create PHY %d\n", i);
+ return PTR_ERR(phy->phy);
+ }
+
+ phy->index = i;
+ phy_set_drvdata(phy->phy, &data->phys[i]);
+ }
+
+ data->id_det_irq = gpiod_to_irq(data->id_det_gpio);
+ if (data->id_det_irq > 0) {
+ ret = devm_request_irq(dev, data->id_det_irq,
+ sun4i_usb_phy0_id_vbus_det_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "usb0-id-det", data);
+ if (ret) {
+ dev_err(dev, "Err requesting id-det-irq: %d\n", ret);
+ return ret;
+ }
+ }
+
+ data->vbus_det_irq = gpiod_to_irq(data->vbus_det_gpio);
+ if (data->vbus_det_irq > 0) {
+ ret = devm_request_irq(dev, data->vbus_det_irq,
+ sun4i_usb_phy0_id_vbus_det_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "usb0-vbus-det", data);
+ if (ret) {
+ dev_err(dev, "Err requesting vbus-det-irq: %d\n", ret);
+ data->vbus_det_irq = -1;
+ sun4i_usb_phy_remove(pdev); /* Stop detect work */
+ return ret;
+ }
+ }
+
+ if (data->vbus_power_supply) {
+ data->vbus_power_nb.notifier_call = sun4i_usb_phy0_vbus_notify;
+ data->vbus_power_nb.priority = 0;
+ ret = power_supply_reg_notifier(&data->vbus_power_nb);
+ if (ret) {
+ sun4i_usb_phy_remove(pdev); /* Stop detect work */
+ return ret;
+ }
+ data->vbus_power_nb_registered = true;
+ }
+
+ phy_provider = devm_of_phy_provider_register(dev, sun4i_usb_phy_xlate);
+ if (IS_ERR(phy_provider)) {
+ sun4i_usb_phy_remove(pdev); /* Stop detect work */
+ return PTR_ERR(phy_provider);
+ }
+
+ dev_dbg(dev, "successfully loaded\n");
+
+ return 0;
+}
+
+static const struct sun4i_usb_phy_cfg sun4i_a10_cfg = {
+ .num_phys = 3,
+ .type = sun4i_a10_phy,
+ .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 = {
+ .num_phys = 2,
+ .type = sun4i_a10_phy,
+ .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 = {
+ .num_phys = 3,
+ .type = sun6i_a31_phy,
+ .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 = {
+ .num_phys = 3,
+ .type = sun4i_a10_phy,
+ .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 = {
+ .num_phys = 2,
+ .type = sun6i_a31_phy,
+ .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 = {
+ .num_phys = 2,
+ .type = sun8i_a33_phy,
+ .disc_thresh = 3,
+ .phyctl_offset = REG_PHYCTL_A33,
+ .dedicated_clocks = true,
+ .enable_pmu_unk1 = false,
+};
+
+static const struct sun4i_usb_phy_cfg sun8i_a83t_cfg = {
+ .num_phys = 3,
+ .hsic_index = 2,
+ .type = sun8i_a83t_phy,
+ .phyctl_offset = REG_PHYCTL_A33,
+ .dedicated_clocks = true,
+};
+
+static const struct sun4i_usb_phy_cfg sun8i_h3_cfg = {
+ .num_phys = 4,
+ .type = sun8i_h3_phy,
+ .disc_thresh = 3,
+ .phyctl_offset = REG_PHYCTL_A33,
+ .dedicated_clocks = true,
+ .enable_pmu_unk1 = true,
+ .phy0_dual_route = true,
+};
+
+static const struct sun4i_usb_phy_cfg sun8i_r40_cfg = {
+ .num_phys = 3,
+ .type = sun8i_r40_phy,
+ .disc_thresh = 3,
+ .phyctl_offset = REG_PHYCTL_A33,
+ .dedicated_clocks = true,
+ .enable_pmu_unk1 = true,
+ .phy0_dual_route = true,
+};
+
+static const struct sun4i_usb_phy_cfg sun8i_v3s_cfg = {
+ .num_phys = 1,
+ .type = sun8i_v3s_phy,
+ .disc_thresh = 3,
+ .phyctl_offset = REG_PHYCTL_A33,
+ .dedicated_clocks = true,
+ .enable_pmu_unk1 = true,
+ .phy0_dual_route = true,
+};
+
+static const struct sun4i_usb_phy_cfg sun50i_a64_cfg = {
+ .num_phys = 2,
+ .type = sun50i_a64_phy,
+ .disc_thresh = 3,
+ .phyctl_offset = REG_PHYCTL_A33,
+ .dedicated_clocks = true,
+ .enable_pmu_unk1 = true,
+ .phy0_dual_route = true,
+};
+
+static const struct of_device_id sun4i_usb_phy_of_match[] = {
+ { .compatible = "allwinner,sun4i-a10-usb-phy", .data = &sun4i_a10_cfg },
+ { .compatible = "allwinner,sun5i-a13-usb-phy", .data = &sun5i_a13_cfg },
+ { .compatible = "allwinner,sun6i-a31-usb-phy", .data = &sun6i_a31_cfg },
+ { .compatible = "allwinner,sun7i-a20-usb-phy", .data = &sun7i_a20_cfg },
+ { .compatible = "allwinner,sun8i-a23-usb-phy", .data = &sun8i_a23_cfg },
+ { .compatible = "allwinner,sun8i-a33-usb-phy", .data = &sun8i_a33_cfg },
+ { .compatible = "allwinner,sun8i-a83t-usb-phy", .data = &sun8i_a83t_cfg },
+ { .compatible = "allwinner,sun8i-h3-usb-phy", .data = &sun8i_h3_cfg },
+ { .compatible = "allwinner,sun8i-r40-usb-phy", .data = &sun8i_r40_cfg },
+ { .compatible = "allwinner,sun8i-v3s-usb-phy", .data = &sun8i_v3s_cfg },
+ { .compatible = "allwinner,sun50i-a64-usb-phy",
+ .data = &sun50i_a64_cfg},
+ { },
+};
+MODULE_DEVICE_TABLE(of, sun4i_usb_phy_of_match);
+
+static struct platform_driver sun4i_usb_phy_driver = {
+ .probe = sun4i_usb_phy_probe,
+ .remove = sun4i_usb_phy_remove,
+ .driver = {
+ .of_match_table = sun4i_usb_phy_of_match,
+ .name = "sun4i-usb-phy",
+ }
+};
+module_platform_driver(sun4i_usb_phy_driver);
+
+MODULE_DESCRIPTION("Allwinner sun4i USB phy driver");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/allwinner/phy-sun9i-usb.c b/drivers/phy/allwinner/phy-sun9i-usb.c
new file mode 100644
index 000000000..28fce4bce
--- /dev/null
+++ b/drivers/phy/allwinner/phy-sun9i-usb.c
@@ -0,0 +1,202 @@
+/*
+ * Allwinner sun9i USB phy driver
+ *
+ * Copyright (C) 2014-2015 Chen-Yu Tsai <wens@csie.org>
+ *
+ * Based on phy-sun4i-usb.c from
+ * Hans de Goede <hdegoede@redhat.com>
+ *
+ * and code from
+ * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
+ *
+ * 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/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/usb/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#define SUNXI_AHB_INCR16_BURST_EN BIT(11)
+#define SUNXI_AHB_INCR8_BURST_EN BIT(10)
+#define SUNXI_AHB_INCR4_BURST_EN BIT(9)
+#define SUNXI_AHB_INCRX_ALIGN_EN BIT(8)
+#define SUNXI_ULPI_BYPASS_EN BIT(0)
+
+/* usb1 HSIC specific bits */
+#define SUNXI_EHCI_HS_FORCE BIT(20)
+#define SUNXI_HSIC_CONNECT_DET BIT(17)
+#define SUNXI_HSIC_CONNECT_INT BIT(16)
+#define SUNXI_HSIC BIT(1)
+
+struct sun9i_usb_phy {
+ struct phy *phy;
+ void __iomem *pmu;
+ struct reset_control *reset;
+ struct clk *clk;
+ struct clk *hsic_clk;
+ enum usb_phy_interface type;
+};
+
+static void sun9i_usb_phy_passby(struct sun9i_usb_phy *phy, int enable)
+{
+ u32 bits, reg_value;
+
+ bits = SUNXI_AHB_INCR16_BURST_EN | SUNXI_AHB_INCR8_BURST_EN |
+ SUNXI_AHB_INCR4_BURST_EN | SUNXI_AHB_INCRX_ALIGN_EN |
+ SUNXI_ULPI_BYPASS_EN;
+
+ if (phy->type == USBPHY_INTERFACE_MODE_HSIC)
+ bits |= SUNXI_HSIC | SUNXI_EHCI_HS_FORCE |
+ SUNXI_HSIC_CONNECT_DET | SUNXI_HSIC_CONNECT_INT;
+
+ reg_value = readl(phy->pmu);
+
+ if (enable)
+ reg_value |= bits;
+ else
+ reg_value &= ~bits;
+
+ writel(reg_value, phy->pmu);
+}
+
+static int sun9i_usb_phy_init(struct phy *_phy)
+{
+ struct sun9i_usb_phy *phy = phy_get_drvdata(_phy);
+ int ret;
+
+ ret = clk_prepare_enable(phy->clk);
+ if (ret)
+ goto err_clk;
+
+ ret = clk_prepare_enable(phy->hsic_clk);
+ if (ret)
+ goto err_hsic_clk;
+
+ ret = reset_control_deassert(phy->reset);
+ if (ret)
+ goto err_reset;
+
+ sun9i_usb_phy_passby(phy, 1);
+ return 0;
+
+err_reset:
+ clk_disable_unprepare(phy->hsic_clk);
+
+err_hsic_clk:
+ clk_disable_unprepare(phy->clk);
+
+err_clk:
+ return ret;
+}
+
+static int sun9i_usb_phy_exit(struct phy *_phy)
+{
+ struct sun9i_usb_phy *phy = phy_get_drvdata(_phy);
+
+ sun9i_usb_phy_passby(phy, 0);
+ reset_control_assert(phy->reset);
+ clk_disable_unprepare(phy->hsic_clk);
+ clk_disable_unprepare(phy->clk);
+
+ return 0;
+}
+
+static const struct phy_ops sun9i_usb_phy_ops = {
+ .init = sun9i_usb_phy_init,
+ .exit = sun9i_usb_phy_exit,
+ .owner = THIS_MODULE,
+};
+
+static int sun9i_usb_phy_probe(struct platform_device *pdev)
+{
+ struct sun9i_usb_phy *phy;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct phy_provider *phy_provider;
+ struct resource *res;
+
+ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ phy->type = of_usb_get_phy_mode(np);
+ if (phy->type == USBPHY_INTERFACE_MODE_HSIC) {
+ phy->clk = devm_clk_get(dev, "hsic_480M");
+ if (IS_ERR(phy->clk)) {
+ dev_err(dev, "failed to get hsic_480M clock\n");
+ return PTR_ERR(phy->clk);
+ }
+
+ phy->hsic_clk = devm_clk_get(dev, "hsic_12M");
+ if (IS_ERR(phy->hsic_clk)) {
+ dev_err(dev, "failed to get hsic_12M clock\n");
+ return PTR_ERR(phy->hsic_clk);
+ }
+
+ phy->reset = devm_reset_control_get(dev, "hsic");
+ if (IS_ERR(phy->reset)) {
+ dev_err(dev, "failed to get reset control\n");
+ return PTR_ERR(phy->reset);
+ }
+ } else {
+ phy->clk = devm_clk_get(dev, "phy");
+ if (IS_ERR(phy->clk)) {
+ dev_err(dev, "failed to get phy clock\n");
+ return PTR_ERR(phy->clk);
+ }
+
+ phy->reset = devm_reset_control_get(dev, "phy");
+ if (IS_ERR(phy->reset)) {
+ dev_err(dev, "failed to get reset control\n");
+ return PTR_ERR(phy->reset);
+ }
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ phy->pmu = devm_ioremap_resource(dev, res);
+ if (IS_ERR(phy->pmu))
+ return PTR_ERR(phy->pmu);
+
+ phy->phy = devm_phy_create(dev, NULL, &sun9i_usb_phy_ops);
+ if (IS_ERR(phy->phy)) {
+ dev_err(dev, "failed to create PHY\n");
+ return PTR_ERR(phy->phy);
+ }
+
+ phy_set_drvdata(phy->phy, phy);
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id sun9i_usb_phy_of_match[] = {
+ { .compatible = "allwinner,sun9i-a80-usb-phy" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, sun9i_usb_phy_of_match);
+
+static struct platform_driver sun9i_usb_phy_driver = {
+ .probe = sun9i_usb_phy_probe,
+ .driver = {
+ .of_match_table = sun9i_usb_phy_of_match,
+ .name = "sun9i-usb-phy",
+ }
+};
+module_platform_driver(sun9i_usb_phy_driver);
+
+MODULE_DESCRIPTION("Allwinner sun9i USB phy driver");
+MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/amlogic/Kconfig b/drivers/phy/amlogic/Kconfig
new file mode 100644
index 000000000..23fe1cda2
--- /dev/null
+++ b/drivers/phy/amlogic/Kconfig
@@ -0,0 +1,38 @@
+#
+# Phy drivers for Amlogic platforms
+#
+config PHY_MESON8B_USB2
+ tristate "Meson8, Meson8b and GXBB USB2 PHY driver"
+ default ARCH_MESON
+ depends on OF && (ARCH_MESON || COMPILE_TEST)
+ depends on USB_SUPPORT
+ select USB_COMMON
+ select GENERIC_PHY
+ help
+ Enable this to support the Meson USB2 PHYs found in Meson8,
+ Meson8b and GXBB SoCs.
+ If unsure, say N.
+
+config PHY_MESON_GXL_USB2
+ tristate "Meson GXL and GXM USB2 PHY drivers"
+ default ARCH_MESON
+ depends on OF && (ARCH_MESON || COMPILE_TEST)
+ depends on USB_SUPPORT
+ select GENERIC_PHY
+ select REGMAP_MMIO
+ help
+ Enable this to support the Meson USB2 PHYs found in Meson
+ GXL and GXM SoCs.
+ If unsure, say N.
+
+config PHY_MESON_GXL_USB3
+ tristate "Meson GXL and GXM USB3 PHY drivers"
+ default ARCH_MESON
+ depends on OF && (ARCH_MESON || COMPILE_TEST)
+ depends on USB_SUPPORT
+ select GENERIC_PHY
+ select REGMAP_MMIO
+ help
+ Enable this to support the Meson USB3 PHY and OTG detection
+ IP block found in Meson GXL and GXM SoCs.
+ If unsure, say N.
diff --git a/drivers/phy/amlogic/Makefile b/drivers/phy/amlogic/Makefile
new file mode 100644
index 000000000..4fd8848c1
--- /dev/null
+++ b/drivers/phy/amlogic/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_PHY_MESON8B_USB2) += phy-meson8b-usb2.o
+obj-$(CONFIG_PHY_MESON_GXL_USB2) += phy-meson-gxl-usb2.o
+obj-$(CONFIG_PHY_MESON_GXL_USB3) += phy-meson-gxl-usb3.o
diff --git a/drivers/phy/amlogic/phy-meson-gxl-usb2.c b/drivers/phy/amlogic/phy-meson-gxl-usb2.c
new file mode 100644
index 000000000..9f9b5414b
--- /dev/null
+++ b/drivers/phy/amlogic/phy-meson-gxl-usb2.c
@@ -0,0 +1,309 @@
+/*
+ * Meson GXL and GXM USB2 PHY driver
+ *
+ * Copyright (C) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+/* bits [31:27] are read-only */
+#define U2P_R0 0x0
+ #define U2P_R0_BYPASS_SEL BIT(0)
+ #define U2P_R0_BYPASS_DM_EN BIT(1)
+ #define U2P_R0_BYPASS_DP_EN BIT(2)
+ #define U2P_R0_TXBITSTUFF_ENH BIT(3)
+ #define U2P_R0_TXBITSTUFF_EN BIT(4)
+ #define U2P_R0_DM_PULLDOWN BIT(5)
+ #define U2P_R0_DP_PULLDOWN BIT(6)
+ #define U2P_R0_DP_VBUS_VLD_EXT_SEL BIT(7)
+ #define U2P_R0_DP_VBUS_VLD_EXT BIT(8)
+ #define U2P_R0_ADP_PRB_EN BIT(9)
+ #define U2P_R0_ADP_DISCHARGE BIT(10)
+ #define U2P_R0_ADP_CHARGE BIT(11)
+ #define U2P_R0_DRV_VBUS BIT(12)
+ #define U2P_R0_ID_PULLUP BIT(13)
+ #define U2P_R0_LOOPBACK_EN_B BIT(14)
+ #define U2P_R0_OTG_DISABLE BIT(15)
+ #define U2P_R0_COMMON_ONN BIT(16)
+ #define U2P_R0_FSEL_MASK GENMASK(19, 17)
+ #define U2P_R0_REF_CLK_SEL_MASK GENMASK(21, 20)
+ #define U2P_R0_POWER_ON_RESET BIT(22)
+ #define U2P_R0_V_ATE_TEST_EN_B_MASK GENMASK(24, 23)
+ #define U2P_R0_ID_SET_ID_DQ BIT(25)
+ #define U2P_R0_ATE_RESET BIT(26)
+ #define U2P_R0_FSV_MINUS BIT(27)
+ #define U2P_R0_FSV_PLUS BIT(28)
+ #define U2P_R0_BYPASS_DM_DATA BIT(29)
+ #define U2P_R0_BYPASS_DP_DATA BIT(30)
+
+#define U2P_R1 0x4
+ #define U2P_R1_BURN_IN_TEST BIT(0)
+ #define U2P_R1_ACA_ENABLE BIT(1)
+ #define U2P_R1_DCD_ENABLE BIT(2)
+ #define U2P_R1_VDAT_SRC_EN_B BIT(3)
+ #define U2P_R1_VDAT_DET_EN_B BIT(4)
+ #define U2P_R1_CHARGES_SEL BIT(5)
+ #define U2P_R1_TX_PREEMP_PULSE_TUNE BIT(6)
+ #define U2P_R1_TX_PREEMP_AMP_TUNE_MASK GENMASK(8, 7)
+ #define U2P_R1_TX_RES_TUNE_MASK GENMASK(10, 9)
+ #define U2P_R1_TX_RISE_TUNE_MASK GENMASK(12, 11)
+ #define U2P_R1_TX_VREF_TUNE_MASK GENMASK(16, 13)
+ #define U2P_R1_TX_FSLS_TUNE_MASK GENMASK(20, 17)
+ #define U2P_R1_TX_HSXV_TUNE_MASK GENMASK(22, 21)
+ #define U2P_R1_OTG_TUNE_MASK GENMASK(25, 23)
+ #define U2P_R1_SQRX_TUNE_MASK GENMASK(28, 26)
+ #define U2P_R1_COMP_DIS_TUNE_MASK GENMASK(31, 29)
+
+/* bits [31:14] are read-only */
+#define U2P_R2 0x8
+ #define U2P_R2_TESTDATA_IN_MASK GENMASK(7, 0)
+ #define U2P_R2_TESTADDR_MASK GENMASK(11, 8)
+ #define U2P_R2_TESTDATA_OUT_SEL BIT(12)
+ #define U2P_R2_TESTCLK BIT(13)
+ #define U2P_R2_TESTDATA_OUT_MASK GENMASK(17, 14)
+ #define U2P_R2_ACA_PIN_RANGE_C BIT(18)
+ #define U2P_R2_ACA_PIN_RANGE_B BIT(19)
+ #define U2P_R2_ACA_PIN_RANGE_A BIT(20)
+ #define U2P_R2_ACA_PIN_GND BIT(21)
+ #define U2P_R2_ACA_PIN_FLOAT BIT(22)
+ #define U2P_R2_CHARGE_DETECT BIT(23)
+ #define U2P_R2_DEVICE_SESSION_VALID BIT(24)
+ #define U2P_R2_ADP_PROBE BIT(25)
+ #define U2P_R2_ADP_SENSE BIT(26)
+ #define U2P_R2_SESSION_END BIT(27)
+ #define U2P_R2_VBUS_VALID BIT(28)
+ #define U2P_R2_B_VALID BIT(29)
+ #define U2P_R2_A_VALID BIT(30)
+ #define U2P_R2_ID_DIG BIT(31)
+
+#define U2P_R3 0xc
+
+#define RESET_COMPLETE_TIME 500
+
+struct phy_meson_gxl_usb2_priv {
+ struct regmap *regmap;
+ enum phy_mode mode;
+ int is_enabled;
+ struct clk *clk;
+ struct reset_control *reset;
+};
+
+static const struct regmap_config phy_meson_gxl_usb2_regmap_conf = {
+ .reg_bits = 8,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = U2P_R3,
+};
+
+static int phy_meson_gxl_usb2_init(struct phy *phy)
+{
+ struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = reset_control_reset(priv->reset);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int phy_meson_gxl_usb2_exit(struct phy *phy)
+{
+ struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy);
+
+ clk_disable_unprepare(priv->clk);
+
+ return 0;
+}
+
+static int phy_meson_gxl_usb2_reset(struct phy *phy)
+{
+ struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy);
+
+ if (priv->is_enabled) {
+ /* reset the PHY and wait until settings are stabilized */
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET,
+ U2P_R0_POWER_ON_RESET);
+ udelay(RESET_COMPLETE_TIME);
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET,
+ 0);
+ udelay(RESET_COMPLETE_TIME);
+ }
+
+ return 0;
+}
+
+static int phy_meson_gxl_usb2_set_mode(struct phy *phy, enum phy_mode mode)
+{
+ struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy);
+
+ switch (mode) {
+ case PHY_MODE_USB_HOST:
+ case PHY_MODE_USB_OTG:
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DM_PULLDOWN,
+ U2P_R0_DM_PULLDOWN);
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DP_PULLDOWN,
+ U2P_R0_DP_PULLDOWN);
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_ID_PULLUP, 0);
+ break;
+
+ case PHY_MODE_USB_DEVICE:
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DM_PULLDOWN,
+ 0);
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DP_PULLDOWN,
+ 0);
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_ID_PULLUP,
+ U2P_R0_ID_PULLUP);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ phy_meson_gxl_usb2_reset(phy);
+
+ priv->mode = mode;
+
+ return 0;
+}
+
+static int phy_meson_gxl_usb2_power_off(struct phy *phy)
+{
+ struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy);
+
+ priv->is_enabled = 0;
+
+ /* power off the PHY by putting it into reset mode */
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET,
+ U2P_R0_POWER_ON_RESET);
+
+ return 0;
+}
+
+static int phy_meson_gxl_usb2_power_on(struct phy *phy)
+{
+ struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ priv->is_enabled = 1;
+
+ /* power on the PHY by taking it out of reset mode */
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET, 0);
+
+ ret = phy_meson_gxl_usb2_set_mode(phy, priv->mode);
+ if (ret) {
+ phy_meson_gxl_usb2_power_off(phy);
+
+ dev_err(&phy->dev, "Failed to initialize PHY with mode %d\n",
+ priv->mode);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct phy_ops phy_meson_gxl_usb2_ops = {
+ .init = phy_meson_gxl_usb2_init,
+ .exit = phy_meson_gxl_usb2_exit,
+ .power_on = phy_meson_gxl_usb2_power_on,
+ .power_off = phy_meson_gxl_usb2_power_off,
+ .set_mode = phy_meson_gxl_usb2_set_mode,
+ .reset = phy_meson_gxl_usb2_reset,
+ .owner = THIS_MODULE,
+};
+
+static int phy_meson_gxl_usb2_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct phy_provider *phy_provider;
+ struct resource *res;
+ struct phy_meson_gxl_usb2_priv *priv;
+ struct phy *phy;
+ void __iomem *base;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ /* start in host mode */
+ priv->mode = PHY_MODE_USB_HOST;
+
+ priv->regmap = devm_regmap_init_mmio(dev, base,
+ &phy_meson_gxl_usb2_regmap_conf);
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
+ priv->clk = devm_clk_get(dev, "phy");
+ if (IS_ERR(priv->clk)) {
+ ret = PTR_ERR(priv->clk);
+ if (ret == -ENOENT)
+ priv->clk = NULL;
+ else
+ return ret;
+ }
+
+ priv->reset = devm_reset_control_get_optional_shared(dev, "phy");
+ if (IS_ERR(priv->reset))
+ return PTR_ERR(priv->reset);
+
+ phy = devm_phy_create(dev, NULL, &phy_meson_gxl_usb2_ops);
+ if (IS_ERR(phy)) {
+ ret = PTR_ERR(phy);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to create PHY\n");
+
+ return ret;
+ }
+
+ phy_set_drvdata(phy, priv);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id phy_meson_gxl_usb2_of_match[] = {
+ { .compatible = "amlogic,meson-gxl-usb2-phy", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, phy_meson_gxl_usb2_of_match);
+
+static struct platform_driver phy_meson_gxl_usb2_driver = {
+ .probe = phy_meson_gxl_usb2_probe,
+ .driver = {
+ .name = "phy-meson-gxl-usb2",
+ .of_match_table = phy_meson_gxl_usb2_of_match,
+ },
+};
+module_platform_driver(phy_meson_gxl_usb2_driver);
+
+MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
+MODULE_DESCRIPTION("Meson GXL and GXM USB2 PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/amlogic/phy-meson-gxl-usb3.c b/drivers/phy/amlogic/phy-meson-gxl-usb3.c
new file mode 100644
index 000000000..d37d94ddf
--- /dev/null
+++ b/drivers/phy/amlogic/phy-meson-gxl-usb3.c
@@ -0,0 +1,282 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Meson GXL USB3 PHY and OTG mode detection driver
+ *
+ * Copyright (C) 2018 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/platform_device.h>
+
+#define USB_R0 0x00
+ #define USB_R0_P30_FSEL_MASK GENMASK(5, 0)
+ #define USB_R0_P30_PHY_RESET BIT(6)
+ #define USB_R0_P30_TEST_POWERDOWN_HSP BIT(7)
+ #define USB_R0_P30_TEST_POWERDOWN_SSP BIT(8)
+ #define USB_R0_P30_ACJT_LEVEL_MASK GENMASK(13, 9)
+ #define USB_R0_P30_TX_BOOST_LEVEL_MASK GENMASK(16, 14)
+ #define USB_R0_P30_LANE0_TX2RX_LOOPBACK BIT(17)
+ #define USB_R0_P30_LANE0_EXT_PCLK_REQ BIT(18)
+ #define USB_R0_P30_PCS_RX_LOS_MASK_VAL_MASK GENMASK(28, 19)
+ #define USB_R0_U2D_SS_SCALEDOWN_MODE_MASK GENMASK(30, 29)
+ #define USB_R0_U2D_ACT BIT(31)
+
+#define USB_R1 0x04
+ #define USB_R1_U3H_BIGENDIAN_GS BIT(0)
+ #define USB_R1_U3H_PME_ENABLE BIT(1)
+ #define USB_R1_U3H_HUB_PORT_OVERCURRENT_MASK GENMASK(6, 2)
+ #define USB_R1_U3H_HUB_PORT_PERM_ATTACH_MASK GENMASK(11, 7)
+ #define USB_R1_U3H_HOST_U2_PORT_DISABLE_MASK GENMASK(15, 12)
+ #define USB_R1_U3H_HOST_U3_PORT_DISABLE BIT(16)
+ #define USB_R1_U3H_HOST_PORT_POWER_CONTROL_PRESENT BIT(17)
+ #define USB_R1_U3H_HOST_MSI_ENABLE BIT(18)
+ #define USB_R1_U3H_FLADJ_30MHZ_REG_MASK GENMASK(24, 19)
+ #define USB_R1_P30_PCS_TX_SWING_FULL_MASK GENMASK(31, 25)
+
+#define USB_R2 0x08
+ #define USB_R2_P30_CR_DATA_IN_MASK GENMASK(15, 0)
+ #define USB_R2_P30_CR_READ BIT(16)
+ #define USB_R2_P30_CR_WRITE BIT(17)
+ #define USB_R2_P30_CR_CAP_ADDR BIT(18)
+ #define USB_R2_P30_CR_CAP_DATA BIT(19)
+ #define USB_R2_P30_PCS_TX_DEEMPH_3P5DB_MASK GENMASK(25, 20)
+ #define USB_R2_P30_PCS_TX_DEEMPH_6DB_MASK GENMASK(31, 26)
+
+#define USB_R3 0x0c
+ #define USB_R3_P30_SSC_ENABLE BIT(0)
+ #define USB_R3_P30_SSC_RANGE_MASK GENMASK(3, 1)
+ #define USB_R3_P30_SSC_REF_CLK_SEL_MASK GENMASK(12, 4)
+ #define USB_R3_P30_REF_SSP_EN BIT(13)
+ #define USB_R3_P30_LOS_BIAS_MASK GENMASK(18, 16)
+ #define USB_R3_P30_LOS_LEVEL_MASK GENMASK(23, 19)
+ #define USB_R3_P30_MPLL_MULTIPLIER_MASK GENMASK(30, 24)
+
+#define USB_R4 0x10
+ #define USB_R4_P21_PORT_RESET_0 BIT(0)
+ #define USB_R4_P21_SLEEP_M0 BIT(1)
+ #define USB_R4_MEM_PD_MASK GENMASK(3, 2)
+ #define USB_R4_P21_ONLY BIT(4)
+
+#define USB_R5 0x14
+ #define USB_R5_ID_DIG_SYNC BIT(0)
+ #define USB_R5_ID_DIG_REG BIT(1)
+ #define USB_R5_ID_DIG_CFG_MASK GENMASK(3, 2)
+ #define USB_R5_ID_DIG_EN_0 BIT(4)
+ #define USB_R5_ID_DIG_EN_1 BIT(5)
+ #define USB_R5_ID_DIG_CURR BIT(6)
+ #define USB_R5_ID_DIG_IRQ BIT(7)
+ #define USB_R5_ID_DIG_TH_MASK GENMASK(15, 8)
+ #define USB_R5_ID_DIG_CNT_MASK GENMASK(23, 16)
+
+/* read-only register */
+#define USB_R6 0x18
+ #define USB_R6_P30_CR_DATA_OUT_MASK GENMASK(15, 0)
+ #define USB_R6_P30_CR_ACK BIT(16)
+
+struct phy_meson_gxl_usb3_priv {
+ struct regmap *regmap;
+ enum phy_mode mode;
+ struct clk *clk_phy;
+ struct clk *clk_peripheral;
+ struct reset_control *reset;
+};
+
+static const struct regmap_config phy_meson_gxl_usb3_regmap_conf = {
+ .reg_bits = 8,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = USB_R6,
+};
+
+static int phy_meson_gxl_usb3_power_on(struct phy *phy)
+{
+ struct phy_meson_gxl_usb3_priv *priv = phy_get_drvdata(phy);
+
+ regmap_update_bits(priv->regmap, USB_R5, USB_R5_ID_DIG_EN_0,
+ USB_R5_ID_DIG_EN_0);
+ regmap_update_bits(priv->regmap, USB_R5, USB_R5_ID_DIG_EN_1,
+ USB_R5_ID_DIG_EN_1);
+ regmap_update_bits(priv->regmap, USB_R5, USB_R5_ID_DIG_TH_MASK,
+ FIELD_PREP(USB_R5_ID_DIG_TH_MASK, 0xff));
+
+ return 0;
+}
+
+static int phy_meson_gxl_usb3_power_off(struct phy *phy)
+{
+ struct phy_meson_gxl_usb3_priv *priv = phy_get_drvdata(phy);
+
+ regmap_update_bits(priv->regmap, USB_R5, USB_R5_ID_DIG_EN_0, 0);
+ regmap_update_bits(priv->regmap, USB_R5, USB_R5_ID_DIG_EN_1, 0);
+
+ return 0;
+}
+
+static int phy_meson_gxl_usb3_set_mode(struct phy *phy, enum phy_mode mode)
+{
+ struct phy_meson_gxl_usb3_priv *priv = phy_get_drvdata(phy);
+
+ switch (mode) {
+ case PHY_MODE_USB_HOST:
+ regmap_update_bits(priv->regmap, USB_R0, USB_R0_U2D_ACT, 0);
+ regmap_update_bits(priv->regmap, USB_R4, USB_R4_P21_SLEEP_M0,
+ 0);
+ break;
+
+ case PHY_MODE_USB_DEVICE:
+ regmap_update_bits(priv->regmap, USB_R0, USB_R0_U2D_ACT,
+ USB_R0_U2D_ACT);
+ regmap_update_bits(priv->regmap, USB_R4, USB_R4_P21_SLEEP_M0,
+ USB_R4_P21_SLEEP_M0);
+ break;
+
+ default:
+ dev_err(&phy->dev, "unsupported PHY mode %d\n", mode);
+ return -EINVAL;
+ }
+
+ priv->mode = mode;
+
+ return 0;
+}
+
+static int phy_meson_gxl_usb3_init(struct phy *phy)
+{
+ struct phy_meson_gxl_usb3_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = reset_control_reset(priv->reset);
+ if (ret)
+ goto err;
+
+ ret = clk_prepare_enable(priv->clk_phy);
+ if (ret)
+ goto err;
+
+ ret = clk_prepare_enable(priv->clk_peripheral);
+ if (ret)
+ goto err_disable_clk_phy;
+
+ ret = phy_meson_gxl_usb3_set_mode(phy, priv->mode);
+ if (ret)
+ goto err_disable_clk_peripheral;
+
+ regmap_update_bits(priv->regmap, USB_R1,
+ USB_R1_U3H_FLADJ_30MHZ_REG_MASK,
+ FIELD_PREP(USB_R1_U3H_FLADJ_30MHZ_REG_MASK, 0x20));
+
+ return 0;
+
+err_disable_clk_peripheral:
+ clk_disable_unprepare(priv->clk_peripheral);
+err_disable_clk_phy:
+ clk_disable_unprepare(priv->clk_phy);
+err:
+ return ret;
+}
+
+static int phy_meson_gxl_usb3_exit(struct phy *phy)
+{
+ struct phy_meson_gxl_usb3_priv *priv = phy_get_drvdata(phy);
+
+ clk_disable_unprepare(priv->clk_peripheral);
+ clk_disable_unprepare(priv->clk_phy);
+
+ return 0;
+}
+
+static const struct phy_ops phy_meson_gxl_usb3_ops = {
+ .power_on = phy_meson_gxl_usb3_power_on,
+ .power_off = phy_meson_gxl_usb3_power_off,
+ .set_mode = phy_meson_gxl_usb3_set_mode,
+ .init = phy_meson_gxl_usb3_init,
+ .exit = phy_meson_gxl_usb3_exit,
+ .owner = THIS_MODULE,
+};
+
+static int phy_meson_gxl_usb3_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct phy_meson_gxl_usb3_priv *priv;
+ struct resource *res;
+ struct phy *phy;
+ struct phy_provider *phy_provider;
+ void __iomem *base;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ priv->regmap = devm_regmap_init_mmio(dev, base,
+ &phy_meson_gxl_usb3_regmap_conf);
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
+ priv->clk_phy = devm_clk_get(dev, "phy");
+ if (IS_ERR(priv->clk_phy))
+ return PTR_ERR(priv->clk_phy);
+
+ priv->clk_peripheral = devm_clk_get(dev, "peripheral");
+ if (IS_ERR(priv->clk_peripheral))
+ return PTR_ERR(priv->clk_peripheral);
+
+ priv->reset = devm_reset_control_array_get_shared(dev);
+ if (IS_ERR(priv->reset))
+ return PTR_ERR(priv->reset);
+
+ /*
+ * default to host mode as hardware defaults and/or boot-loader
+ * behavior can result in this PHY starting up in device mode. this
+ * default and the initialization in phy_meson_gxl_usb3_init ensure
+ * that we reproducibly start in a known mode on all devices.
+ */
+ priv->mode = PHY_MODE_USB_HOST;
+
+ phy = devm_phy_create(dev, np, &phy_meson_gxl_usb3_ops);
+ if (IS_ERR(phy)) {
+ ret = PTR_ERR(phy);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to create PHY\n");
+
+ return ret;
+ }
+
+ phy_set_drvdata(phy, priv);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id phy_meson_gxl_usb3_of_match[] = {
+ { .compatible = "amlogic,meson-gxl-usb3-phy", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, phy_meson_gxl_usb3_of_match);
+
+static struct platform_driver phy_meson_gxl_usb3_driver = {
+ .probe = phy_meson_gxl_usb3_probe,
+ .driver = {
+ .name = "phy-meson-gxl-usb3",
+ .of_match_table = phy_meson_gxl_usb3_of_match,
+ },
+};
+module_platform_driver(phy_meson_gxl_usb3_driver);
+
+MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
+MODULE_DESCRIPTION("Meson GXL USB3 PHY and OTG detection driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/amlogic/phy-meson8b-usb2.c b/drivers/phy/amlogic/phy-meson8b-usb2.c
new file mode 100644
index 000000000..9c01b7e19
--- /dev/null
+++ b/drivers/phy/amlogic/phy-meson8b-usb2.c
@@ -0,0 +1,287 @@
+/*
+ * Meson8, Meson8b and GXBB USB2 PHY driver
+ *
+ * Copyright (C) 2016 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/reset.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/usb/of.h>
+
+#define REG_CONFIG 0x00
+ #define REG_CONFIG_CLK_EN BIT(0)
+ #define REG_CONFIG_CLK_SEL_MASK GENMASK(3, 1)
+ #define REG_CONFIG_CLK_DIV_MASK GENMASK(10, 4)
+ #define REG_CONFIG_CLK_32k_ALTSEL BIT(15)
+ #define REG_CONFIG_TEST_TRIG BIT(31)
+
+#define REG_CTRL 0x04
+ #define REG_CTRL_SOFT_PRST BIT(0)
+ #define REG_CTRL_SOFT_HRESET BIT(1)
+ #define REG_CTRL_SS_SCALEDOWN_MODE_MASK GENMASK(3, 2)
+ #define REG_CTRL_CLK_DET_RST BIT(4)
+ #define REG_CTRL_INTR_SEL BIT(5)
+ #define REG_CTRL_CLK_DETECTED BIT(8)
+ #define REG_CTRL_SOF_SENT_RCVD_TGL BIT(9)
+ #define REG_CTRL_SOF_TOGGLE_OUT BIT(10)
+ #define REG_CTRL_POWER_ON_RESET BIT(15)
+ #define REG_CTRL_SLEEPM BIT(16)
+ #define REG_CTRL_TX_BITSTUFF_ENN_H BIT(17)
+ #define REG_CTRL_TX_BITSTUFF_ENN BIT(18)
+ #define REG_CTRL_COMMON_ON BIT(19)
+ #define REG_CTRL_REF_CLK_SEL_MASK GENMASK(21, 20)
+ #define REG_CTRL_REF_CLK_SEL_SHIFT 20
+ #define REG_CTRL_FSEL_MASK GENMASK(24, 22)
+ #define REG_CTRL_FSEL_SHIFT 22
+ #define REG_CTRL_PORT_RESET BIT(25)
+ #define REG_CTRL_THREAD_ID_MASK GENMASK(31, 26)
+
+#define REG_ENDP_INTR 0x08
+
+/* bits [31:26], [24:21] and [15:3] seem to be read-only */
+#define REG_ADP_BC 0x0c
+ #define REG_ADP_BC_VBUS_VLD_EXT_SEL BIT(0)
+ #define REG_ADP_BC_VBUS_VLD_EXT BIT(1)
+ #define REG_ADP_BC_OTG_DISABLE BIT(2)
+ #define REG_ADP_BC_ID_PULLUP BIT(3)
+ #define REG_ADP_BC_DRV_VBUS BIT(4)
+ #define REG_ADP_BC_ADP_PRB_EN BIT(5)
+ #define REG_ADP_BC_ADP_DISCHARGE BIT(6)
+ #define REG_ADP_BC_ADP_CHARGE BIT(7)
+ #define REG_ADP_BC_SESS_END BIT(8)
+ #define REG_ADP_BC_DEVICE_SESS_VLD BIT(9)
+ #define REG_ADP_BC_B_VALID BIT(10)
+ #define REG_ADP_BC_A_VALID BIT(11)
+ #define REG_ADP_BC_ID_DIG BIT(12)
+ #define REG_ADP_BC_VBUS_VALID BIT(13)
+ #define REG_ADP_BC_ADP_PROBE BIT(14)
+ #define REG_ADP_BC_ADP_SENSE BIT(15)
+ #define REG_ADP_BC_ACA_ENABLE BIT(16)
+ #define REG_ADP_BC_DCD_ENABLE BIT(17)
+ #define REG_ADP_BC_VDAT_DET_EN_B BIT(18)
+ #define REG_ADP_BC_VDAT_SRC_EN_B BIT(19)
+ #define REG_ADP_BC_CHARGE_SEL BIT(20)
+ #define REG_ADP_BC_CHARGE_DETECT BIT(21)
+ #define REG_ADP_BC_ACA_PIN_RANGE_C BIT(22)
+ #define REG_ADP_BC_ACA_PIN_RANGE_B BIT(23)
+ #define REG_ADP_BC_ACA_PIN_RANGE_A BIT(24)
+ #define REG_ADP_BC_ACA_PIN_GND BIT(25)
+ #define REG_ADP_BC_ACA_PIN_FLOAT BIT(26)
+
+#define REG_DBG_UART 0x10
+
+#define REG_TEST 0x14
+ #define REG_TEST_DATA_IN_MASK GENMASK(3, 0)
+ #define REG_TEST_EN_MASK GENMASK(7, 4)
+ #define REG_TEST_ADDR_MASK GENMASK(11, 8)
+ #define REG_TEST_DATA_OUT_SEL BIT(12)
+ #define REG_TEST_CLK BIT(13)
+ #define REG_TEST_VA_TEST_EN_B_MASK GENMASK(15, 14)
+ #define REG_TEST_DATA_OUT_MASK GENMASK(19, 16)
+ #define REG_TEST_DISABLE_ID_PULLUP BIT(20)
+
+#define REG_TUNE 0x18
+ #define REG_TUNE_TX_RES_TUNE_MASK GENMASK(1, 0)
+ #define REG_TUNE_TX_HSXV_TUNE_MASK GENMASK(3, 2)
+ #define REG_TUNE_TX_VREF_TUNE_MASK GENMASK(7, 4)
+ #define REG_TUNE_TX_RISE_TUNE_MASK GENMASK(9, 8)
+ #define REG_TUNE_TX_PREEMP_PULSE_TUNE BIT(10)
+ #define REG_TUNE_TX_PREEMP_AMP_TUNE_MASK GENMASK(12, 11)
+ #define REG_TUNE_TX_FSLS_TUNE_MASK GENMASK(16, 13)
+ #define REG_TUNE_SQRX_TUNE_MASK GENMASK(19, 17)
+ #define REG_TUNE_OTG_TUNE GENMASK(22, 20)
+ #define REG_TUNE_COMP_DIS_TUNE GENMASK(25, 23)
+ #define REG_TUNE_HOST_DM_PULLDOWN BIT(26)
+ #define REG_TUNE_HOST_DP_PULLDOWN BIT(27)
+
+#define RESET_COMPLETE_TIME 500
+#define ACA_ENABLE_COMPLETE_TIME 50
+
+struct phy_meson8b_usb2_priv {
+ void __iomem *regs;
+ enum usb_dr_mode dr_mode;
+ struct clk *clk_usb_general;
+ struct clk *clk_usb;
+ struct reset_control *reset;
+};
+
+static u32 phy_meson8b_usb2_read(struct phy_meson8b_usb2_priv *phy_priv,
+ u32 reg)
+{
+ return readl(phy_priv->regs + reg);
+}
+
+static void phy_meson8b_usb2_mask_bits(struct phy_meson8b_usb2_priv *phy_priv,
+ u32 reg, u32 mask, u32 value)
+{
+ u32 data;
+
+ data = phy_meson8b_usb2_read(phy_priv, reg);
+ data &= ~mask;
+ data |= (value & mask);
+
+ writel(data, phy_priv->regs + reg);
+}
+
+static int phy_meson8b_usb2_power_on(struct phy *phy)
+{
+ struct phy_meson8b_usb2_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ if (!IS_ERR_OR_NULL(priv->reset)) {
+ ret = reset_control_reset(priv->reset);
+ if (ret) {
+ dev_err(&phy->dev, "Failed to trigger USB reset\n");
+ return ret;
+ }
+ }
+
+ ret = clk_prepare_enable(priv->clk_usb_general);
+ if (ret) {
+ dev_err(&phy->dev, "Failed to enable USB general clock\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(priv->clk_usb);
+ if (ret) {
+ dev_err(&phy->dev, "Failed to enable USB DDR clock\n");
+ clk_disable_unprepare(priv->clk_usb_general);
+ return ret;
+ }
+
+ phy_meson8b_usb2_mask_bits(priv, REG_CONFIG, REG_CONFIG_CLK_32k_ALTSEL,
+ REG_CONFIG_CLK_32k_ALTSEL);
+
+ phy_meson8b_usb2_mask_bits(priv, REG_CTRL, REG_CTRL_REF_CLK_SEL_MASK,
+ 0x2 << REG_CTRL_REF_CLK_SEL_SHIFT);
+
+ phy_meson8b_usb2_mask_bits(priv, REG_CTRL, REG_CTRL_FSEL_MASK,
+ 0x5 << REG_CTRL_FSEL_SHIFT);
+
+ /* reset the PHY */
+ phy_meson8b_usb2_mask_bits(priv, REG_CTRL, REG_CTRL_POWER_ON_RESET,
+ REG_CTRL_POWER_ON_RESET);
+ udelay(RESET_COMPLETE_TIME);
+ phy_meson8b_usb2_mask_bits(priv, REG_CTRL, REG_CTRL_POWER_ON_RESET, 0);
+ udelay(RESET_COMPLETE_TIME);
+
+ phy_meson8b_usb2_mask_bits(priv, REG_CTRL, REG_CTRL_SOF_TOGGLE_OUT,
+ REG_CTRL_SOF_TOGGLE_OUT);
+
+ if (priv->dr_mode == USB_DR_MODE_HOST) {
+ phy_meson8b_usb2_mask_bits(priv, REG_ADP_BC,
+ REG_ADP_BC_ACA_ENABLE,
+ REG_ADP_BC_ACA_ENABLE);
+
+ udelay(ACA_ENABLE_COMPLETE_TIME);
+
+ if (phy_meson8b_usb2_read(priv, REG_ADP_BC) &
+ REG_ADP_BC_ACA_PIN_FLOAT) {
+ dev_warn(&phy->dev, "USB ID detect failed!\n");
+ clk_disable_unprepare(priv->clk_usb);
+ clk_disable_unprepare(priv->clk_usb_general);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int phy_meson8b_usb2_power_off(struct phy *phy)
+{
+ struct phy_meson8b_usb2_priv *priv = phy_get_drvdata(phy);
+
+ clk_disable_unprepare(priv->clk_usb);
+ clk_disable_unprepare(priv->clk_usb_general);
+
+ return 0;
+}
+
+static const struct phy_ops phy_meson8b_usb2_ops = {
+ .power_on = phy_meson8b_usb2_power_on,
+ .power_off = phy_meson8b_usb2_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int phy_meson8b_usb2_probe(struct platform_device *pdev)
+{
+ struct phy_meson8b_usb2_priv *priv;
+ struct resource *res;
+ struct phy *phy;
+ struct phy_provider *phy_provider;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->regs))
+ return PTR_ERR(priv->regs);
+
+ priv->clk_usb_general = devm_clk_get(&pdev->dev, "usb_general");
+ if (IS_ERR(priv->clk_usb_general))
+ return PTR_ERR(priv->clk_usb_general);
+
+ priv->clk_usb = devm_clk_get(&pdev->dev, "usb");
+ if (IS_ERR(priv->clk_usb))
+ return PTR_ERR(priv->clk_usb);
+
+ priv->reset = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
+ if (PTR_ERR(priv->reset) == -EPROBE_DEFER)
+ return PTR_ERR(priv->reset);
+
+ priv->dr_mode = of_usb_get_dr_mode_by_phy(pdev->dev.of_node, -1);
+ if (priv->dr_mode == USB_DR_MODE_UNKNOWN) {
+ dev_err(&pdev->dev,
+ "missing dual role configuration of the controller\n");
+ return -EINVAL;
+ }
+
+ phy = devm_phy_create(&pdev->dev, NULL, &phy_meson8b_usb2_ops);
+ if (IS_ERR(phy)) {
+ dev_err(&pdev->dev, "failed to create PHY\n");
+ return PTR_ERR(phy);
+ }
+
+ phy_set_drvdata(phy, priv);
+
+ phy_provider =
+ devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id phy_meson8b_usb2_of_match[] = {
+ { .compatible = "amlogic,meson8-usb2-phy", },
+ { .compatible = "amlogic,meson8b-usb2-phy", },
+ { .compatible = "amlogic,meson-gxbb-usb2-phy", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, phy_meson8b_usb2_of_match);
+
+static struct platform_driver phy_meson8b_usb2_driver = {
+ .probe = phy_meson8b_usb2_probe,
+ .driver = {
+ .name = "phy-meson-usb2",
+ .of_match_table = phy_meson8b_usb2_of_match,
+ },
+};
+module_platform_driver(phy_meson8b_usb2_driver);
+
+MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
+MODULE_DESCRIPTION("Meson8, Meson8b and GXBB USB2 PHY driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig
new file mode 100644
index 000000000..aa917a610
--- /dev/null
+++ b/drivers/phy/broadcom/Kconfig
@@ -0,0 +1,93 @@
+#
+# Phy drivers for Broadcom platforms
+#
+config PHY_CYGNUS_PCIE
+ tristate "Broadcom Cygnus PCIe PHY driver"
+ depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST)
+ select GENERIC_PHY
+ default ARCH_BCM_CYGNUS
+ help
+ Enable this to support the Broadcom Cygnus PCIe PHY.
+ If unsure, say N.
+
+config BCM_KONA_USB2_PHY
+ tristate "Broadcom Kona USB2 PHY Driver"
+ depends on HAS_IOMEM
+ select GENERIC_PHY
+ help
+ Enable this to support the Broadcom Kona USB 2.0 PHY.
+
+config PHY_BCM_NS_USB2
+ tristate "Broadcom Northstar USB 2.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 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
+ depends on MDIO_BUS
+ select GENERIC_PHY
+ help
+ Enable this to support Broadcom USB 3.0 PHY connected to the USB
+ controller on Northstar family.
+
+config PHY_NS2_PCIE
+ tristate "Broadcom Northstar2 PCIe PHY driver"
+ depends on OF && MDIO_BUS_MUX_BCM_IPROC
+ select GENERIC_PHY
+ default ARCH_BCM_IPROC
+ help
+ Enable this to support the Broadcom Northstar2 PCIe PHY.
+ If unsure, say N.
+
+config PHY_NS2_USB_DRD
+ tristate "Broadcom Northstar2 USB DRD PHY support"
+ depends on OF && (ARCH_BCM_IPROC || COMPILE_TEST)
+ select GENERIC_PHY
+ select EXTCON
+ default ARCH_BCM_IPROC
+ help
+ Enable this to support the Broadcom Northstar2 USB DRD PHY.
+ This driver initializes the PHY in either HOST or DEVICE mode.
+ The host or device configuration is read from device tree.
+
+ If unsure, say N.
+
+config PHY_BRCM_SATA
+ tristate "Broadcom SATA PHY driver"
+ depends on ARCH_BRCMSTB || ARCH_BCM_IPROC || BMIPS_GENERIC || \
+ ARCH_BCM_63XX || COMPILE_TEST
+ depends on OF
+ select GENERIC_PHY
+ default ARCH_BCM_IPROC
+ help
+ Enable this to support the Broadcom SATA PHY.
+ If unsure, say N.
+
+config PHY_BRCM_USB
+ tristate "Broadcom STB USB PHY driver"
+ depends on ARCH_BRCMSTB
+ depends on OF
+ select GENERIC_PHY
+ select SOC_BRCMSTB
+ default ARCH_BRCMSTB
+ help
+ Enable this to support the Broadcom STB USB PHY.
+ This driver is required by the USB XHCI, EHCI and OHCI
+ drivers.
+ If unsure, say N.
+
+config PHY_BCM_SR_PCIE
+ tristate "Broadcom Stingray PCIe PHY driver"
+ depends on OF && (ARCH_BCM_IPROC || COMPILE_TEST)
+ select GENERIC_PHY
+ select MFD_SYSCON
+ default ARCH_BCM_IPROC
+ help
+ Enable this to support the Broadcom Stingray PCIe PHY
+ If unsure, say N.
diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile
new file mode 100644
index 000000000..0f60184e6
--- /dev/null
+++ b/drivers/phy/broadcom/Makefile
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_PHY_CYGNUS_PCIE) += phy-bcm-cygnus-pcie.o
+obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o
+obj-$(CONFIG_PHY_BCM_NS_USB2) += phy-bcm-ns-usb2.o
+obj-$(CONFIG_PHY_BCM_NS_USB3) += phy-bcm-ns-usb3.o
+obj-$(CONFIG_PHY_NS2_PCIE) += phy-bcm-ns2-pcie.o
+obj-$(CONFIG_PHY_NS2_USB_DRD) += phy-bcm-ns2-usbdrd.o
+obj-$(CONFIG_PHY_BRCM_SATA) += phy-brcm-sata.o
+obj-$(CONFIG_PHY_BRCM_USB) += phy-brcm-usb-dvr.o
+
+phy-brcm-usb-dvr-objs := phy-brcm-usb.o phy-brcm-usb-init.o
+
+obj-$(CONFIG_PHY_BCM_SR_PCIE) += phy-bcm-sr-pcie.o
diff --git a/drivers/phy/broadcom/phy-bcm-cygnus-pcie.c b/drivers/phy/broadcom/phy-bcm-cygnus-pcie.c
new file mode 100644
index 000000000..0f4ac5d63
--- /dev/null
+++ b/drivers/phy/broadcom/phy-bcm-cygnus-pcie.c
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2015 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+#define PCIE_CFG_OFFSET 0x00
+#define PCIE1_PHY_IDDQ_SHIFT 10
+#define PCIE0_PHY_IDDQ_SHIFT 2
+
+enum cygnus_pcie_phy_id {
+ CYGNUS_PHY_PCIE0 = 0,
+ CYGNUS_PHY_PCIE1,
+ MAX_NUM_PHYS,
+};
+
+struct cygnus_pcie_phy_core;
+
+/**
+ * struct cygnus_pcie_phy - Cygnus PCIe PHY device
+ * @core: pointer to the Cygnus PCIe PHY core control
+ * @id: internal ID to identify the Cygnus PCIe PHY
+ * @phy: pointer to the kernel PHY device
+ */
+struct cygnus_pcie_phy {
+ struct cygnus_pcie_phy_core *core;
+ enum cygnus_pcie_phy_id id;
+ struct phy *phy;
+};
+
+/**
+ * struct cygnus_pcie_phy_core - Cygnus PCIe PHY core control
+ * @dev: pointer to device
+ * @base: base register
+ * @lock: mutex to protect access to individual PHYs
+ * @phys: pointer to Cygnus PHY device
+ */
+struct cygnus_pcie_phy_core {
+ struct device *dev;
+ void __iomem *base;
+ struct mutex lock;
+ struct cygnus_pcie_phy phys[MAX_NUM_PHYS];
+};
+
+static int cygnus_pcie_power_config(struct cygnus_pcie_phy *phy, bool enable)
+{
+ struct cygnus_pcie_phy_core *core = phy->core;
+ unsigned shift;
+ u32 val;
+
+ mutex_lock(&core->lock);
+
+ switch (phy->id) {
+ case CYGNUS_PHY_PCIE0:
+ shift = PCIE0_PHY_IDDQ_SHIFT;
+ break;
+
+ case CYGNUS_PHY_PCIE1:
+ shift = PCIE1_PHY_IDDQ_SHIFT;
+ break;
+
+ default:
+ mutex_unlock(&core->lock);
+ dev_err(core->dev, "PCIe PHY %d invalid\n", phy->id);
+ return -EINVAL;
+ }
+
+ if (enable) {
+ val = readl(core->base + PCIE_CFG_OFFSET);
+ val &= ~BIT(shift);
+ writel(val, core->base + PCIE_CFG_OFFSET);
+ /*
+ * Wait 50 ms for the PCIe Serdes to stabilize after the analog
+ * front end is brought up
+ */
+ msleep(50);
+ } else {
+ val = readl(core->base + PCIE_CFG_OFFSET);
+ val |= BIT(shift);
+ writel(val, core->base + PCIE_CFG_OFFSET);
+ }
+
+ mutex_unlock(&core->lock);
+ dev_dbg(core->dev, "PCIe PHY %d %s\n", phy->id,
+ enable ? "enabled" : "disabled");
+ return 0;
+}
+
+static int cygnus_pcie_phy_power_on(struct phy *p)
+{
+ struct cygnus_pcie_phy *phy = phy_get_drvdata(p);
+
+ return cygnus_pcie_power_config(phy, true);
+}
+
+static int cygnus_pcie_phy_power_off(struct phy *p)
+{
+ struct cygnus_pcie_phy *phy = phy_get_drvdata(p);
+
+ return cygnus_pcie_power_config(phy, false);
+}
+
+static const struct phy_ops cygnus_pcie_phy_ops = {
+ .power_on = cygnus_pcie_phy_power_on,
+ .power_off = cygnus_pcie_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int cygnus_pcie_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *node = dev->of_node, *child;
+ struct cygnus_pcie_phy_core *core;
+ struct phy_provider *provider;
+ struct resource *res;
+ unsigned cnt = 0;
+ int ret;
+
+ if (of_get_child_count(node) == 0) {
+ dev_err(dev, "PHY no child node\n");
+ return -ENODEV;
+ }
+
+ core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL);
+ if (!core)
+ return -ENOMEM;
+
+ core->dev = dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ core->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(core->base))
+ return PTR_ERR(core->base);
+
+ mutex_init(&core->lock);
+
+ for_each_available_child_of_node(node, child) {
+ unsigned int id;
+ struct cygnus_pcie_phy *p;
+
+ if (of_property_read_u32(child, "reg", &id)) {
+ dev_err(dev, "missing reg property for %s\n",
+ child->name);
+ ret = -EINVAL;
+ goto put_child;
+ }
+
+ if (id >= MAX_NUM_PHYS) {
+ dev_err(dev, "invalid PHY id: %u\n", id);
+ ret = -EINVAL;
+ goto put_child;
+ }
+
+ if (core->phys[id].phy) {
+ dev_err(dev, "duplicated PHY id: %u\n", id);
+ ret = -EINVAL;
+ goto put_child;
+ }
+
+ p = &core->phys[id];
+ p->phy = devm_phy_create(dev, child, &cygnus_pcie_phy_ops);
+ if (IS_ERR(p->phy)) {
+ dev_err(dev, "failed to create PHY\n");
+ ret = PTR_ERR(p->phy);
+ goto put_child;
+ }
+
+ p->core = core;
+ p->id = id;
+ phy_set_drvdata(p->phy, p);
+ cnt++;
+ }
+
+ dev_set_drvdata(dev, core);
+
+ provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(provider)) {
+ dev_err(dev, "failed to register PHY provider\n");
+ return PTR_ERR(provider);
+ }
+
+ dev_dbg(dev, "registered %u PCIe PHY(s)\n", cnt);
+
+ return 0;
+put_child:
+ of_node_put(child);
+ return ret;
+}
+
+static const struct of_device_id cygnus_pcie_phy_match_table[] = {
+ { .compatible = "brcm,cygnus-pcie-phy" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, cygnus_pcie_phy_match_table);
+
+static struct platform_driver cygnus_pcie_phy_driver = {
+ .driver = {
+ .name = "cygnus-pcie-phy",
+ .of_match_table = cygnus_pcie_phy_match_table,
+ },
+ .probe = cygnus_pcie_phy_probe,
+};
+module_platform_driver(cygnus_pcie_phy_driver);
+
+MODULE_AUTHOR("Ray Jui <rjui@broadcom.com>");
+MODULE_DESCRIPTION("Broadcom Cygnus PCIe PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/broadcom/phy-bcm-kona-usb2.c b/drivers/phy/broadcom/phy-bcm-kona-usb2.c
new file mode 100644
index 000000000..7b67fe49e
--- /dev/null
+++ b/drivers/phy/broadcom/phy-bcm-kona-usb2.c
@@ -0,0 +1,155 @@
+/*
+ * phy-bcm-kona-usb2.c - Broadcom Kona USB2 Phy Driver
+ *
+ * Copyright (C) 2013 Linaro Limited
+ * Matt Porter <mporter@linaro.org>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+#define OTGCTL (0)
+#define OTGCTL_OTGSTAT2 BIT(31)
+#define OTGCTL_OTGSTAT1 BIT(30)
+#define OTGCTL_PRST_N_SW BIT(11)
+#define OTGCTL_HRESET_N BIT(10)
+#define OTGCTL_UTMI_LINE_STATE1 BIT(9)
+#define OTGCTL_UTMI_LINE_STATE0 BIT(8)
+
+#define P1CTL (8)
+#define P1CTL_SOFT_RESET BIT(1)
+#define P1CTL_NON_DRIVING BIT(0)
+
+struct bcm_kona_usb {
+ void __iomem *regs;
+};
+
+static void bcm_kona_usb_phy_power(struct bcm_kona_usb *phy, int on)
+{
+ u32 val;
+
+ val = readl(phy->regs + OTGCTL);
+ if (on) {
+ /* Configure and power PHY */
+ val &= ~(OTGCTL_OTGSTAT2 | OTGCTL_OTGSTAT1 |
+ OTGCTL_UTMI_LINE_STATE1 | OTGCTL_UTMI_LINE_STATE0);
+ val |= OTGCTL_PRST_N_SW | OTGCTL_HRESET_N;
+ } else {
+ val &= ~(OTGCTL_PRST_N_SW | OTGCTL_HRESET_N);
+ }
+ writel(val, phy->regs + OTGCTL);
+}
+
+static int bcm_kona_usb_phy_init(struct phy *gphy)
+{
+ struct bcm_kona_usb *phy = phy_get_drvdata(gphy);
+ u32 val;
+
+ /* Soft reset PHY */
+ val = readl(phy->regs + P1CTL);
+ val &= ~P1CTL_NON_DRIVING;
+ val |= P1CTL_SOFT_RESET;
+ writel(val, phy->regs + P1CTL);
+ writel(val & ~P1CTL_SOFT_RESET, phy->regs + P1CTL);
+ /* Reset needs to be asserted for 2ms */
+ mdelay(2);
+ writel(val | P1CTL_SOFT_RESET, phy->regs + P1CTL);
+
+ return 0;
+}
+
+static int bcm_kona_usb_phy_power_on(struct phy *gphy)
+{
+ struct bcm_kona_usb *phy = phy_get_drvdata(gphy);
+
+ bcm_kona_usb_phy_power(phy, 1);
+
+ return 0;
+}
+
+static int bcm_kona_usb_phy_power_off(struct phy *gphy)
+{
+ struct bcm_kona_usb *phy = phy_get_drvdata(gphy);
+
+ bcm_kona_usb_phy_power(phy, 0);
+
+ return 0;
+}
+
+static const struct phy_ops ops = {
+ .init = bcm_kona_usb_phy_init,
+ .power_on = bcm_kona_usb_phy_power_on,
+ .power_off = bcm_kona_usb_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int bcm_kona_usb2_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct bcm_kona_usb *phy;
+ struct resource *res;
+ struct phy *gphy;
+ struct phy_provider *phy_provider;
+
+ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ phy->regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(phy->regs))
+ return PTR_ERR(phy->regs);
+
+ platform_set_drvdata(pdev, phy);
+
+ gphy = devm_phy_create(dev, NULL, &ops);
+ if (IS_ERR(gphy))
+ return PTR_ERR(gphy);
+
+ /* The Kona PHY supports an 8-bit wide UTMI interface */
+ phy_set_bus_width(gphy, 8);
+
+ phy_set_drvdata(gphy, phy);
+
+ phy_provider = devm_of_phy_provider_register(dev,
+ of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id bcm_kona_usb2_dt_ids[] = {
+ { .compatible = "brcm,kona-usb2-phy" },
+ { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, bcm_kona_usb2_dt_ids);
+
+static struct platform_driver bcm_kona_usb2_driver = {
+ .probe = bcm_kona_usb2_probe,
+ .driver = {
+ .name = "bcm-kona-usb2",
+ .of_match_table = bcm_kona_usb2_dt_ids,
+ },
+};
+
+module_platform_driver(bcm_kona_usb2_driver);
+
+MODULE_ALIAS("platform:bcm-kona-usb2");
+MODULE_AUTHOR("Matt Porter <mporter@linaro.org>");
+MODULE_DESCRIPTION("BCM Kona USB 2.0 PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/broadcom/phy-bcm-ns-usb2.c b/drivers/phy/broadcom/phy-bcm-ns-usb2.c
new file mode 100644
index 000000000..58dff80e9
--- /dev/null
+++ b/drivers/phy/broadcom/phy-bcm-ns-usb2.c
@@ -0,0 +1,137 @@
+/*
+ * Broadcom Northstar USB 2.0 PHY Driver
+ *
+ * Copyright (C) 2016 Rafał Miłecki <zajec5@gmail.com>
+ *
+ * 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/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+struct bcm_ns_usb2 {
+ struct device *dev;
+ struct clk *ref_clk;
+ struct phy *phy;
+ void __iomem *dmu;
+};
+
+static int bcm_ns_usb2_phy_init(struct phy *phy)
+{
+ struct bcm_ns_usb2 *usb2 = phy_get_drvdata(phy);
+ struct device *dev = usb2->dev;
+ void __iomem *dmu = usb2->dmu;
+ u32 ref_clk_rate, usb2ctl, usb_pll_ndiv, usb_pll_pdiv;
+ int err = 0;
+
+ err = clk_prepare_enable(usb2->ref_clk);
+ if (err < 0) {
+ dev_err(dev, "Failed to prepare ref clock: %d\n", err);
+ goto err_out;
+ }
+
+ ref_clk_rate = clk_get_rate(usb2->ref_clk);
+ if (!ref_clk_rate) {
+ dev_err(dev, "Failed to get ref clock rate\n");
+ err = -EINVAL;
+ goto err_clk_off;
+ }
+
+ usb2ctl = readl(dmu + BCMA_DMU_CRU_USB2_CONTROL);
+
+ if (usb2ctl & BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_MASK) {
+ usb_pll_pdiv = usb2ctl;
+ usb_pll_pdiv &= BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_MASK;
+ usb_pll_pdiv >>= BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_SHIFT;
+ } else {
+ usb_pll_pdiv = 1 << 3;
+ }
+
+ /* Calculate ndiv based on a solid 1920 MHz that is for USB2 PHY */
+ usb_pll_ndiv = (1920000000 * usb_pll_pdiv) / ref_clk_rate;
+
+ /* Unlock DMU PLL settings with some magic value */
+ writel(0x0000ea68, dmu + BCMA_DMU_CRU_CLKSET_KEY);
+
+ /* Write USB 2.0 PLL control setting */
+ usb2ctl &= ~BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_NDIV_MASK;
+ usb2ctl |= usb_pll_ndiv << BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_NDIV_SHIFT;
+ writel(usb2ctl, dmu + BCMA_DMU_CRU_USB2_CONTROL);
+
+ /* Lock DMU PLL settings */
+ writel(0x00000000, dmu + BCMA_DMU_CRU_CLKSET_KEY);
+
+err_clk_off:
+ clk_disable_unprepare(usb2->ref_clk);
+err_out:
+ return err;
+}
+
+static const struct phy_ops ops = {
+ .init = bcm_ns_usb2_phy_init,
+ .owner = THIS_MODULE,
+};
+
+static int bcm_ns_usb2_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct bcm_ns_usb2 *usb2;
+ struct resource *res;
+ struct phy_provider *phy_provider;
+
+ usb2 = devm_kzalloc(&pdev->dev, sizeof(*usb2), GFP_KERNEL);
+ if (!usb2)
+ return -ENOMEM;
+ usb2->dev = dev;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dmu");
+ usb2->dmu = devm_ioremap_resource(dev, res);
+ if (IS_ERR(usb2->dmu)) {
+ dev_err(dev, "Failed to map DMU regs\n");
+ return PTR_ERR(usb2->dmu);
+ }
+
+ usb2->ref_clk = devm_clk_get(dev, "phy-ref-clk");
+ if (IS_ERR(usb2->ref_clk)) {
+ dev_err(dev, "Clock not defined\n");
+ return PTR_ERR(usb2->ref_clk);
+ }
+
+ usb2->phy = devm_phy_create(dev, NULL, &ops);
+ if (IS_ERR(usb2->phy))
+ return PTR_ERR(usb2->phy);
+
+ phy_set_drvdata(usb2->phy, usb2);
+ platform_set_drvdata(pdev, usb2);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id bcm_ns_usb2_id_table[] = {
+ { .compatible = "brcm,ns-usb2-phy", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, bcm_ns_usb2_id_table);
+
+static struct platform_driver bcm_ns_usb2_driver = {
+ .probe = bcm_ns_usb2_probe,
+ .driver = {
+ .name = "bcm_ns_usb2",
+ .of_match_table = bcm_ns_usb2_id_table,
+ },
+};
+module_platform_driver(bcm_ns_usb2_driver);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/broadcom/phy-bcm-ns-usb3.c b/drivers/phy/broadcom/phy-bcm-ns-usb3.c
new file mode 100644
index 000000000..a53ae128e
--- /dev/null
+++ b/drivers/phy/broadcom/phy-bcm-ns-usb3.c
@@ -0,0 +1,405 @@
+/*
+ * Broadcom Northstar USB 3.0 PHY Driver
+ *
+ * Copyright (C) 2016 Rafał Miłecki <rafal@milecki.pl>
+ * Copyright (C) 2016 Broadcom
+ *
+ * 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/mdio.h>
+#include <linux/module.h>
+#include <linux/of_address.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 */
+
+#define BCM_NS_USB3_PHY_BASE_ADDR_REG 0x1f
+#define BCM_NS_USB3_PHY_PLL30_BLOCK 0x8000
+#define BCM_NS_USB3_PHY_TX_PMD_BLOCK 0x8040
+#define BCM_NS_USB3_PHY_PIPE_BLOCK 0x8060
+
+/* Registers of PLL30 block */
+#define BCM_NS_USB3_PLL_CONTROL 0x01
+#define BCM_NS_USB3_PLLA_CONTROL0 0x0a
+#define BCM_NS_USB3_PLLA_CONTROL1 0x0b
+
+/* Registers of TX PMD block */
+#define BCM_NS_USB3_TX_PMD_CONTROL1 0x01
+
+/* Registers of PIPE block */
+#define BCM_NS_USB3_LFPS_CMP 0x02
+#define BCM_NS_USB3_LFPS_DEGLITCH 0x03
+
+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 mdio_device *mdiodev;
+ struct phy *phy;
+
+ int (*phy_write)(struct bcm_ns_usb3 *usb3, u16 reg, u16 value);
+};
+
+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_mdio_phy_write(struct bcm_ns_usb3 *usb3, u16 reg,
+ u16 value)
+{
+ return usb3->phy_write(usb3, reg, value);
+}
+
+static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3)
+{
+ int err;
+
+ /* USB3 PLL Block */
+ err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG,
+ BCM_NS_USB3_PHY_PLL30_BLOCK);
+ if (err < 0)
+ return err;
+
+ /* Assert Ana_Pllseq start */
+ bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLL_CONTROL, 0x1000);
+
+ /* Assert CML Divider ratio to 26 */
+ bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL0, 0x6400);
+
+ /* Asserting PLL Reset */
+ bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0xc000);
+
+ /* Deaaserting PLL Reset */
+ bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0x8000);
+
+ /* Deasserting USB3 system reset */
+ writel(0, usb3->dmp + BCMA_RESET_CTL);
+
+ /* PLL frequency monitor enable */
+ bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLL_CONTROL, 0x9000);
+
+ /* PIPE Block */
+ bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG,
+ BCM_NS_USB3_PHY_PIPE_BLOCK);
+
+ /* CMPMAX & CMPMINTH setting */
+ bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_LFPS_CMP, 0xf30d);
+
+ /* DEGLITCH MIN & MAX setting */
+ bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_LFPS_DEGLITCH, 0x6302);
+
+ /* TXPMD block */
+ bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG,
+ BCM_NS_USB3_PHY_TX_PMD_BLOCK);
+
+ /* Enabling SSC */
+ bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003);
+
+ return 0;
+}
+
+static int bcm_ns_usb3_phy_init_ns_ax(struct bcm_ns_usb3 *usb3)
+{
+ int err;
+
+ /* PLL30 block */
+ err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG,
+ BCM_NS_USB3_PHY_PLL30_BLOCK);
+ if (err < 0)
+ return err;
+
+ bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL0, 0x6400);
+
+ bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, 0x80e0);
+
+ bcm_ns_usb3_mdio_phy_write(usb3, 0x02, 0x009c);
+
+ /* Enable SSC */
+ bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG,
+ BCM_NS_USB3_PHY_TX_PMD_BLOCK);
+
+ bcm_ns_usb3_mdio_phy_write(usb3, 0x02, 0x21d3);
+
+ bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003);
+
+ /* 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,
+};
+
+/**************************************************
+ * MDIO driver code
+ **************************************************/
+
+static int bcm_ns_usb3_mdiodev_phy_write(struct bcm_ns_usb3 *usb3, u16 reg,
+ u16 value)
+{
+ struct mdio_device *mdiodev = usb3->mdiodev;
+
+ return mdiobus_write(mdiodev->bus, mdiodev->addr, reg, value);
+}
+
+static int bcm_ns_usb3_mdio_probe(struct mdio_device *mdiodev)
+{
+ struct device *dev = &mdiodev->dev;
+ const struct of_device_id *of_id;
+ struct phy_provider *phy_provider;
+ struct device_node *syscon_np;
+ struct bcm_ns_usb3 *usb3;
+ struct resource res;
+ int err;
+
+ usb3 = devm_kzalloc(dev, sizeof(*usb3), GFP_KERNEL);
+ if (!usb3)
+ return -ENOMEM;
+
+ usb3->dev = dev;
+ usb3->mdiodev = mdiodev;
+
+ 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;
+
+ syscon_np = of_parse_phandle(dev->of_node, "usb3-dmp-syscon", 0);
+ err = of_address_to_resource(syscon_np, 0, &res);
+ of_node_put(syscon_np);
+ if (err)
+ return err;
+
+ 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);
+ }
+
+ usb3->phy_write = bcm_ns_usb3_mdiodev_phy_write;
+
+ 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);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static struct mdio_driver bcm_ns_usb3_mdio_driver = {
+ .mdiodrv = {
+ .driver = {
+ .name = "bcm_ns_mdio_usb3",
+ .of_match_table = bcm_ns_usb3_id_table,
+ },
+ },
+ .probe = bcm_ns_usb3_mdio_probe,
+};
+
+/**************************************************
+ * Platform driver code
+ **************************************************/
+
+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_platform_phy_write(struct bcm_ns_usb3 *usb3, u16 reg,
+ u16 value)
+{
+ u32 tmp = 0;
+ 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;
+ }
+
+ /* TODO: Use a proper MDIO bus layer */
+ tmp |= 0x58020000; /* Magic value for MDIO PHY write */
+ tmp |= reg << 18;
+ tmp |= value;
+ writel(tmp, usb3->ccb_mii + BCMA_CCB_MII_MNG_CMD_DATA);
+
+ return bcm_ns_usb3_mii_mng_wait_idle(usb3);
+}
+
+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);
+ }
+
+ /* Enable MDIO. Setting MDCDIV as 26 */
+ writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL);
+
+ /* Wait for MDIO? */
+ udelay(2);
+
+ usb3->phy_write = bcm_ns_usb3_platform_phy_write;
+
+ 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,
+ },
+};
+
+static int __init bcm_ns_usb3_module_init(void)
+{
+ int err;
+
+ /*
+ * For backward compatibility we register as MDIO and platform driver.
+ * After getting MDIO binding commonly used (e.g. switching all DT files
+ * to use it) we should deprecate the old binding and eventually drop
+ * support for it.
+ */
+
+ err = mdio_driver_register(&bcm_ns_usb3_mdio_driver);
+ if (err)
+ return err;
+
+ err = platform_driver_register(&bcm_ns_usb3_driver);
+ if (err)
+ mdio_driver_unregister(&bcm_ns_usb3_mdio_driver);
+
+ return err;
+}
+module_init(bcm_ns_usb3_module_init);
+
+static void __exit bcm_ns_usb3_module_exit(void)
+{
+ platform_driver_unregister(&bcm_ns_usb3_driver);
+ mdio_driver_unregister(&bcm_ns_usb3_mdio_driver);
+}
+module_exit(bcm_ns_usb3_module_exit)
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/broadcom/phy-bcm-ns2-pcie.c b/drivers/phy/broadcom/phy-bcm-ns2-pcie.c
new file mode 100644
index 000000000..4c7d11d2b
--- /dev/null
+++ b/drivers/phy/broadcom/phy-bcm-ns2-pcie.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 Broadcom
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of_mdio.h>
+#include <linux/mdio.h>
+#include <linux/phy.h>
+#include <linux/phy/phy.h>
+
+#define BLK_ADDR_REG_OFFSET 0x1f
+#define PLL_AFE1_100MHZ_BLK 0x2100
+#define PLL_CLK_AMP_OFFSET 0x03
+#define PLL_CLK_AMP_2P05V 0x2b18
+
+static int ns2_pci_phy_init(struct phy *p)
+{
+ struct mdio_device *mdiodev = phy_get_drvdata(p);
+ int rc;
+
+ /* select the AFE 100MHz block page */
+ 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(mdiodev->bus, mdiodev->addr,
+ PLL_CLK_AMP_OFFSET, PLL_CLK_AMP_2P05V);
+ if (rc)
+ goto err;
+
+ return 0;
+
+err:
+ dev_err(&mdiodev->dev, "Error %d writing to phy\n", rc);
+ return rc;
+}
+
+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 phy *phy;
+
+ phy = devm_phy_create(dev, dev->of_node, &ns2_pci_phy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create Phy\n");
+ return PTR_ERR(phy);
+ }
+
+ phy_set_drvdata(phy, mdiodev);
+
+ provider = devm_of_phy_provider_register(&phy->dev,
+ of_phy_simple_xlate);
+ if (IS_ERR(provider)) {
+ dev_err(dev, "failed to register Phy provider\n");
+ return PTR_ERR(provider);
+ }
+
+ dev_info(dev, "%s PHY registered\n", dev_name(dev));
+
+ return 0;
+}
+
+static const struct of_device_id ns2_pci_phy_of_match[] = {
+ { .compatible = "brcm,ns2-pcie-phy", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, ns2_pci_phy_of_match);
+
+static struct mdio_driver ns2_pci_phy_driver = {
+ .mdiodrv = {
+ .driver = {
+ .name = "phy-bcm-ns2-pci",
+ .of_match_table = ns2_pci_phy_of_match,
+ },
+ },
+ .probe = ns2_pci_phy_probe,
+};
+mdio_module_driver(ns2_pci_phy_driver);
+
+MODULE_AUTHOR("Broadcom");
+MODULE_DESCRIPTION("Broadcom Northstar2 PCI Phy driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:phy-bcm-ns2-pci");
diff --git a/drivers/phy/broadcom/phy-bcm-ns2-usbdrd.c b/drivers/phy/broadcom/phy-bcm-ns2-usbdrd.c
new file mode 100644
index 000000000..7ceea5ae2
--- /dev/null
+++ b/drivers/phy/broadcom/phy-bcm-ns2-usbdrd.c
@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 2017 Broadcom
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/extcon-provider.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#define ICFG_DRD_AFE 0x0
+#define ICFG_MISC_STAT 0x18
+#define ICFG_DRD_P0CTL 0x1C
+#define ICFG_STRAP_CTRL 0x20
+#define ICFG_FSM_CTRL 0x24
+
+#define ICFG_DEV_BIT BIT(2)
+#define IDM_RST_BIT BIT(0)
+#define AFE_CORERDY_VDDC BIT(18)
+#define PHY_PLL_RESETB BIT(15)
+#define PHY_RESETB BIT(14)
+#define PHY_PLL_LOCK BIT(0)
+
+#define DRD_DEV_MODE BIT(20)
+#define OHCI_OVRCUR_POL BIT(11)
+#define ICFG_OFF_MODE BIT(6)
+#define PLL_LOCK_RETRY 1000
+
+#define EVT_DEVICE 0
+#define EVT_HOST 1
+
+#define DRD_HOST_MODE (BIT(2) | BIT(3))
+#define DRD_DEVICE_MODE (BIT(4) | BIT(5))
+#define DRD_HOST_VAL 0x803
+#define DRD_DEV_VAL 0x807
+#define GPIO_DELAY 20
+
+struct ns2_phy_data;
+struct ns2_phy_driver {
+ void __iomem *icfgdrd_regs;
+ void __iomem *idmdrd_rst_ctrl;
+ void __iomem *crmu_usb2_ctrl;
+ void __iomem *usb2h_strap_reg;
+ struct ns2_phy_data *data;
+ struct extcon_dev *edev;
+ struct gpio_desc *vbus_gpiod;
+ struct gpio_desc *id_gpiod;
+ int id_irq;
+ int vbus_irq;
+ unsigned long debounce_jiffies;
+ struct delayed_work wq_extcon;
+};
+
+struct ns2_phy_data {
+ struct ns2_phy_driver *driver;
+ struct phy *phy;
+ int new_state;
+};
+
+static const unsigned int usb_extcon_cable[] = {
+ EXTCON_USB,
+ EXTCON_USB_HOST,
+ EXTCON_NONE,
+};
+
+static inline int pll_lock_stat(u32 usb_reg, int reg_mask,
+ struct ns2_phy_driver *driver)
+{
+ int retry = PLL_LOCK_RETRY;
+ u32 val;
+
+ do {
+ udelay(1);
+ val = readl(driver->icfgdrd_regs + usb_reg);
+ if (val & reg_mask)
+ return 0;
+ } while (--retry > 0);
+
+ return -EBUSY;
+}
+
+static int ns2_drd_phy_init(struct phy *phy)
+{
+ struct ns2_phy_data *data = phy_get_drvdata(phy);
+ struct ns2_phy_driver *driver = data->driver;
+ u32 val;
+
+ val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL);
+
+ if (data->new_state == EVT_HOST) {
+ val &= ~DRD_DEVICE_MODE;
+ val |= DRD_HOST_MODE;
+ } else {
+ val &= ~DRD_HOST_MODE;
+ val |= DRD_DEVICE_MODE;
+ }
+ writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
+
+ return 0;
+}
+
+static int ns2_drd_phy_poweroff(struct phy *phy)
+{
+ struct ns2_phy_data *data = phy_get_drvdata(phy);
+ struct ns2_phy_driver *driver = data->driver;
+ u32 val;
+
+ val = readl(driver->crmu_usb2_ctrl);
+ val &= ~AFE_CORERDY_VDDC;
+ writel(val, driver->crmu_usb2_ctrl);
+
+ val = readl(driver->crmu_usb2_ctrl);
+ val &= ~DRD_DEV_MODE;
+ writel(val, driver->crmu_usb2_ctrl);
+
+ /* Disable Host and Device Mode */
+ val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL);
+ val &= ~(DRD_HOST_MODE | DRD_DEVICE_MODE | ICFG_OFF_MODE);
+ writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
+
+ return 0;
+}
+
+static int ns2_drd_phy_poweron(struct phy *phy)
+{
+ struct ns2_phy_data *data = phy_get_drvdata(phy);
+ struct ns2_phy_driver *driver = data->driver;
+ u32 extcon_event = data->new_state;
+ int ret;
+ u32 val;
+
+ if (extcon_event == EVT_DEVICE) {
+ writel(DRD_DEV_VAL, driver->icfgdrd_regs + ICFG_DRD_P0CTL);
+
+ val = readl(driver->idmdrd_rst_ctrl);
+ val &= ~IDM_RST_BIT;
+ writel(val, driver->idmdrd_rst_ctrl);
+
+ val = readl(driver->crmu_usb2_ctrl);
+ val |= (AFE_CORERDY_VDDC | DRD_DEV_MODE);
+ writel(val, driver->crmu_usb2_ctrl);
+
+ /* Bring PHY and PHY_PLL out of Reset */
+ val = readl(driver->crmu_usb2_ctrl);
+ val |= (PHY_PLL_RESETB | PHY_RESETB);
+ writel(val, driver->crmu_usb2_ctrl);
+
+ ret = pll_lock_stat(ICFG_MISC_STAT, PHY_PLL_LOCK, driver);
+ if (ret < 0) {
+ dev_err(&phy->dev, "Phy PLL lock failed\n");
+ return ret;
+ }
+ } else {
+ writel(DRD_HOST_VAL, driver->icfgdrd_regs + ICFG_DRD_P0CTL);
+
+ val = readl(driver->crmu_usb2_ctrl);
+ val |= AFE_CORERDY_VDDC;
+ writel(val, driver->crmu_usb2_ctrl);
+
+ ret = pll_lock_stat(ICFG_MISC_STAT, PHY_PLL_LOCK, driver);
+ if (ret < 0) {
+ dev_err(&phy->dev, "Phy PLL lock failed\n");
+ return ret;
+ }
+
+ val = readl(driver->idmdrd_rst_ctrl);
+ val &= ~IDM_RST_BIT;
+ writel(val, driver->idmdrd_rst_ctrl);
+
+ /* port over current Polarity */
+ val = readl(driver->usb2h_strap_reg);
+ val |= OHCI_OVRCUR_POL;
+ writel(val, driver->usb2h_strap_reg);
+ }
+
+ return 0;
+}
+
+static void connect_change(struct ns2_phy_driver *driver)
+{
+ u32 extcon_event;
+ u32 val;
+
+ extcon_event = driver->data->new_state;
+ val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL);
+
+ switch (extcon_event) {
+ case EVT_DEVICE:
+ val &= ~(DRD_HOST_MODE | DRD_DEVICE_MODE);
+ writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
+
+ val = (val & ~DRD_HOST_MODE) | DRD_DEVICE_MODE;
+ writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
+
+ val = readl(driver->icfgdrd_regs + ICFG_DRD_P0CTL);
+ val |= ICFG_DEV_BIT;
+ writel(val, driver->icfgdrd_regs + ICFG_DRD_P0CTL);
+ break;
+
+ case EVT_HOST:
+ val &= ~(DRD_HOST_MODE | DRD_DEVICE_MODE);
+ writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
+
+ val = (val & ~DRD_DEVICE_MODE) | DRD_HOST_MODE;
+ writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
+
+ val = readl(driver->usb2h_strap_reg);
+ val |= OHCI_OVRCUR_POL;
+ writel(val, driver->usb2h_strap_reg);
+
+ val = readl(driver->icfgdrd_regs + ICFG_DRD_P0CTL);
+ val &= ~ICFG_DEV_BIT;
+ writel(val, driver->icfgdrd_regs + ICFG_DRD_P0CTL);
+ break;
+
+ default:
+ pr_err("Invalid extcon event\n");
+ break;
+ }
+}
+
+static void extcon_work(struct work_struct *work)
+{
+ struct ns2_phy_driver *driver;
+ int vbus;
+ int id;
+
+ driver = container_of(to_delayed_work(work),
+ struct ns2_phy_driver, wq_extcon);
+
+ id = gpiod_get_value_cansleep(driver->id_gpiod);
+ vbus = gpiod_get_value_cansleep(driver->vbus_gpiod);
+
+ if (!id && vbus) { /* Host connected */
+ extcon_set_state_sync(driver->edev, EXTCON_USB_HOST, true);
+ pr_debug("Host cable connected\n");
+ driver->data->new_state = EVT_HOST;
+ connect_change(driver);
+ } else if (id && !vbus) { /* Disconnected */
+ extcon_set_state_sync(driver->edev, EXTCON_USB_HOST, false);
+ extcon_set_state_sync(driver->edev, EXTCON_USB, false);
+ pr_debug("Cable disconnected\n");
+ } else if (id && vbus) { /* Device connected */
+ extcon_set_state_sync(driver->edev, EXTCON_USB, true);
+ pr_debug("Device cable connected\n");
+ driver->data->new_state = EVT_DEVICE;
+ connect_change(driver);
+ }
+}
+
+static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
+{
+ struct ns2_phy_driver *driver = dev_id;
+
+ queue_delayed_work(system_power_efficient_wq, &driver->wq_extcon,
+ driver->debounce_jiffies);
+
+ return IRQ_HANDLED;
+}
+
+static struct phy_ops ops = {
+ .init = ns2_drd_phy_init,
+ .power_on = ns2_drd_phy_poweron,
+ .power_off = ns2_drd_phy_poweroff,
+ .owner = THIS_MODULE,
+};
+
+static const struct of_device_id ns2_drd_phy_dt_ids[] = {
+ { .compatible = "brcm,ns2-drd-phy", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ns2_drd_phy_dt_ids);
+
+static int ns2_drd_phy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct ns2_phy_driver *driver;
+ struct ns2_phy_data *data;
+ struct resource *res;
+ int ret;
+ u32 val;
+
+ driver = devm_kzalloc(dev, sizeof(struct ns2_phy_driver),
+ GFP_KERNEL);
+ if (!driver)
+ return -ENOMEM;
+
+ driver->data = devm_kzalloc(dev, sizeof(struct ns2_phy_data),
+ GFP_KERNEL);
+ if (!driver->data)
+ return -ENOMEM;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "icfg");
+ driver->icfgdrd_regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(driver->icfgdrd_regs))
+ return PTR_ERR(driver->icfgdrd_regs);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rst-ctrl");
+ driver->idmdrd_rst_ctrl = devm_ioremap_resource(dev, res);
+ if (IS_ERR(driver->idmdrd_rst_ctrl))
+ return PTR_ERR(driver->idmdrd_rst_ctrl);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "crmu-ctrl");
+ driver->crmu_usb2_ctrl = devm_ioremap_resource(dev, res);
+ if (IS_ERR(driver->crmu_usb2_ctrl))
+ return PTR_ERR(driver->crmu_usb2_ctrl);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2-strap");
+ driver->usb2h_strap_reg = devm_ioremap_resource(dev, res);
+ if (IS_ERR(driver->usb2h_strap_reg))
+ return PTR_ERR(driver->usb2h_strap_reg);
+
+ /* create extcon */
+ driver->id_gpiod = devm_gpiod_get(&pdev->dev, "id", GPIOD_IN);
+ if (IS_ERR(driver->id_gpiod)) {
+ dev_err(dev, "failed to get ID GPIO\n");
+ return PTR_ERR(driver->id_gpiod);
+ }
+ driver->vbus_gpiod = devm_gpiod_get(&pdev->dev, "vbus", GPIOD_IN);
+ if (IS_ERR(driver->vbus_gpiod)) {
+ dev_err(dev, "failed to get VBUS GPIO\n");
+ return PTR_ERR(driver->vbus_gpiod);
+ }
+
+ driver->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable);
+ if (IS_ERR(driver->edev)) {
+ dev_err(dev, "failed to allocate extcon device\n");
+ return -ENOMEM;
+ }
+
+ ret = devm_extcon_dev_register(dev, driver->edev);
+ if (ret < 0) {
+ dev_err(dev, "failed to register extcon device\n");
+ return ret;
+ }
+
+ ret = gpiod_set_debounce(driver->id_gpiod, GPIO_DELAY * 1000);
+ if (ret < 0)
+ driver->debounce_jiffies = msecs_to_jiffies(GPIO_DELAY);
+
+ INIT_DELAYED_WORK(&driver->wq_extcon, extcon_work);
+
+ driver->id_irq = gpiod_to_irq(driver->id_gpiod);
+ if (driver->id_irq < 0) {
+ dev_err(dev, "failed to get ID IRQ\n");
+ return driver->id_irq;
+ }
+
+ driver->vbus_irq = gpiod_to_irq(driver->vbus_gpiod);
+ if (driver->vbus_irq < 0) {
+ dev_err(dev, "failed to get ID IRQ\n");
+ return driver->vbus_irq;
+ }
+
+ ret = devm_request_irq(dev, driver->id_irq, gpio_irq_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "usb_id", driver);
+ if (ret < 0) {
+ dev_err(dev, "failed to request handler for ID IRQ\n");
+ return ret;
+ }
+
+ ret = devm_request_irq(dev, driver->vbus_irq, gpio_irq_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "usb_vbus", driver);
+ if (ret < 0) {
+ dev_err(dev, "failed to request handler for VBUS IRQ\n");
+ return ret;
+ }
+
+ dev_set_drvdata(dev, driver);
+
+ /* Shutdown all ports. They can be powered up as required */
+ val = readl(driver->crmu_usb2_ctrl);
+ val &= ~(AFE_CORERDY_VDDC | PHY_RESETB);
+ writel(val, driver->crmu_usb2_ctrl);
+
+ data = driver->data;
+ data->phy = devm_phy_create(dev, dev->of_node, &ops);
+ if (IS_ERR(data->phy)) {
+ dev_err(dev, "Failed to create usb drd phy\n");
+ return PTR_ERR(data->phy);
+ }
+
+ data->driver = driver;
+ phy_set_drvdata(data->phy, data);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(phy_provider)) {
+ dev_err(dev, "Failed to register as phy provider\n");
+ return PTR_ERR(phy_provider);
+ }
+
+ platform_set_drvdata(pdev, driver);
+
+ dev_info(dev, "Registered NS2 DRD Phy device\n");
+ queue_delayed_work(system_power_efficient_wq, &driver->wq_extcon,
+ driver->debounce_jiffies);
+
+ return 0;
+}
+
+static struct platform_driver ns2_drd_phy_driver = {
+ .probe = ns2_drd_phy_probe,
+ .driver = {
+ .name = "bcm-ns2-usbphy",
+ .of_match_table = of_match_ptr(ns2_drd_phy_dt_ids),
+ },
+};
+module_platform_driver(ns2_drd_phy_driver);
+
+MODULE_ALIAS("platform:bcm-ns2-drd-phy");
+MODULE_AUTHOR("Broadcom");
+MODULE_DESCRIPTION("Broadcom NS2 USB2 PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/broadcom/phy-bcm-sr-pcie.c b/drivers/phy/broadcom/phy-bcm-sr-pcie.c
new file mode 100644
index 000000000..c10e95f86
--- /dev/null
+++ b/drivers/phy/broadcom/phy-bcm-sr-pcie.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2016-2018 Broadcom
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/* we have up to 8 PAXB based RC. The 9th one is always PAXC */
+#define SR_NR_PCIE_PHYS 9
+#define SR_PAXC_PHY_IDX (SR_NR_PCIE_PHYS - 1)
+
+#define PCIE_PIPEMUX_CFG_OFFSET 0x10c
+#define PCIE_PIPEMUX_SELECT_STRAP 0xf
+
+#define CDRU_STRAP_DATA_LSW_OFFSET 0x5c
+#define PCIE_PIPEMUX_SHIFT 19
+#define PCIE_PIPEMUX_MASK 0xf
+
+#define MHB_MEM_PW_PAXC_OFFSET 0x1c0
+#define MHB_PWR_ARR_POWERON 0x8
+#define MHB_PWR_ARR_POWEROK 0x4
+#define MHB_PWR_POWERON 0x2
+#define MHB_PWR_POWEROK 0x1
+#define MHB_PWR_STATUS_MASK (MHB_PWR_ARR_POWERON | \
+ MHB_PWR_ARR_POWEROK | \
+ MHB_PWR_POWERON | \
+ MHB_PWR_POWEROK)
+
+struct sr_pcie_phy_core;
+
+/**
+ * struct sr_pcie_phy - Stingray PCIe PHY
+ *
+ * @core: pointer to the Stingray PCIe PHY core control
+ * @index: PHY index
+ * @phy: pointer to the kernel PHY device
+ */
+struct sr_pcie_phy {
+ struct sr_pcie_phy_core *core;
+ unsigned int index;
+ struct phy *phy;
+};
+
+/**
+ * struct sr_pcie_phy_core - Stingray PCIe PHY core control
+ *
+ * @dev: pointer to device
+ * @base: base register of PCIe SS
+ * @cdru: regmap to the CDRU device
+ * @mhb: regmap to the MHB device
+ * @pipemux: pipemuex strap
+ * @phys: array of PCIe PHYs
+ */
+struct sr_pcie_phy_core {
+ struct device *dev;
+ void __iomem *base;
+ struct regmap *cdru;
+ struct regmap *mhb;
+ u32 pipemux;
+ struct sr_pcie_phy phys[SR_NR_PCIE_PHYS];
+};
+
+/*
+ * PCIe PIPEMUX lookup table
+ *
+ * Each array index represents a PIPEMUX strap setting
+ * The array element represents a bitmap where a set bit means the PCIe
+ * core and associated serdes has been enabled as RC and is available for use
+ */
+static const u8 pipemux_table[] = {
+ /* PIPEMUX = 0, EP 1x16 */
+ 0x00,
+ /* PIPEMUX = 1, EP 2x8 */
+ 0x00,
+ /* PIPEMUX = 2, EP 4x4 */
+ 0x00,
+ /* PIPEMUX = 3, RC 2x8, cores 0, 7 */
+ 0x81,
+ /* PIPEMUX = 4, RC 4x4, cores 0, 1, 6, 7 */
+ 0xc3,
+ /* PIPEMUX = 5, RC 8x2, all 8 cores */
+ 0xff,
+ /* PIPEMUX = 6, RC 3x4 + 2x2, cores 0, 2, 3, 6, 7 */
+ 0xcd,
+ /* PIPEMUX = 7, RC 1x4 + 6x2, cores 0, 2, 3, 4, 5, 6, 7 */
+ 0xfd,
+ /* PIPEMUX = 8, EP 1x8 + RC 4x2, cores 4, 5, 6, 7 */
+ 0xf0,
+ /* PIPEMUX = 9, EP 1x8 + RC 2x4, cores 6, 7 */
+ 0xc0,
+ /* PIPEMUX = 10, EP 2x4 + RC 2x4, cores 1, 6 */
+ 0x42,
+ /* PIPEMUX = 11, EP 2x4 + RC 4x2, cores 2, 3, 4, 5 */
+ 0x3c,
+ /* PIPEMUX = 12, EP 1x4 + RC 6x2, cores 2, 3, 4, 5, 6, 7 */
+ 0xfc,
+ /* PIPEMUX = 13, RC 2x4 + RC 1x4 + 2x2, cores 2, 3, 6 */
+ 0x4c,
+};
+
+/*
+ * Return true if the strap setting is valid
+ */
+static bool pipemux_strap_is_valid(u32 pipemux)
+{
+ return !!(pipemux < ARRAY_SIZE(pipemux_table));
+}
+
+/*
+ * Read the PCIe PIPEMUX from strap
+ */
+static u32 pipemux_strap_read(struct sr_pcie_phy_core *core)
+{
+ u32 pipemux;
+
+ /*
+ * Read PIPEMUX configuration register to determine the pipemux setting
+ *
+ * In the case when the value indicates using HW strap, fall back to
+ * use HW strap
+ */
+ pipemux = readl(core->base + PCIE_PIPEMUX_CFG_OFFSET);
+ pipemux &= PCIE_PIPEMUX_MASK;
+ if (pipemux == PCIE_PIPEMUX_SELECT_STRAP) {
+ regmap_read(core->cdru, CDRU_STRAP_DATA_LSW_OFFSET, &pipemux);
+ pipemux >>= PCIE_PIPEMUX_SHIFT;
+ pipemux &= PCIE_PIPEMUX_MASK;
+ }
+
+ return pipemux;
+}
+
+/*
+ * Given a PIPEMUX strap and PCIe core index, this function returns true if the
+ * PCIe core needs to be enabled
+ */
+static bool pcie_core_is_for_rc(struct sr_pcie_phy *phy)
+{
+ struct sr_pcie_phy_core *core = phy->core;
+ unsigned int core_idx = phy->index;
+
+ return !!((pipemux_table[core->pipemux] >> core_idx) & 0x1);
+}
+
+static int sr_pcie_phy_init(struct phy *p)
+{
+ struct sr_pcie_phy *phy = phy_get_drvdata(p);
+
+ /*
+ * Check whether this PHY is for root complex or not. If yes, return
+ * zero so the host driver can proceed to enumeration. If not, return
+ * an error and that will force the host driver to bail out
+ */
+ if (pcie_core_is_for_rc(phy))
+ return 0;
+
+ return -ENODEV;
+}
+
+static int sr_paxc_phy_init(struct phy *p)
+{
+ struct sr_pcie_phy *phy = phy_get_drvdata(p);
+ struct sr_pcie_phy_core *core = phy->core;
+ unsigned int core_idx = phy->index;
+ u32 val;
+
+ if (core_idx != SR_PAXC_PHY_IDX)
+ return -EINVAL;
+
+ regmap_read(core->mhb, MHB_MEM_PW_PAXC_OFFSET, &val);
+ if ((val & MHB_PWR_STATUS_MASK) != MHB_PWR_STATUS_MASK) {
+ dev_err(core->dev, "PAXC is not powered up\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static const struct phy_ops sr_pcie_phy_ops = {
+ .init = sr_pcie_phy_init,
+ .owner = THIS_MODULE,
+};
+
+static const struct phy_ops sr_paxc_phy_ops = {
+ .init = sr_paxc_phy_init,
+ .owner = THIS_MODULE,
+};
+
+static struct phy *sr_pcie_phy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct sr_pcie_phy_core *core;
+ int phy_idx;
+
+ core = dev_get_drvdata(dev);
+ if (!core)
+ return ERR_PTR(-EINVAL);
+
+ phy_idx = args->args[0];
+
+ if (WARN_ON(phy_idx >= SR_NR_PCIE_PHYS))
+ return ERR_PTR(-ENODEV);
+
+ return core->phys[phy_idx].phy;
+}
+
+static int sr_pcie_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *node = dev->of_node;
+ struct sr_pcie_phy_core *core;
+ struct resource *res;
+ struct phy_provider *provider;
+ unsigned int phy_idx = 0;
+
+ core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL);
+ if (!core)
+ return -ENOMEM;
+
+ core->dev = dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ core->base = devm_ioremap_resource(core->dev, res);
+ if (IS_ERR(core->base))
+ return PTR_ERR(core->base);
+
+ core->cdru = syscon_regmap_lookup_by_phandle(node, "brcm,sr-cdru");
+ if (IS_ERR(core->cdru)) {
+ dev_err(core->dev, "unable to find CDRU device\n");
+ return PTR_ERR(core->cdru);
+ }
+
+ core->mhb = syscon_regmap_lookup_by_phandle(node, "brcm,sr-mhb");
+ if (IS_ERR(core->mhb)) {
+ dev_err(core->dev, "unable to find MHB device\n");
+ return PTR_ERR(core->mhb);
+ }
+
+ /* read the PCIe PIPEMUX strap setting */
+ core->pipemux = pipemux_strap_read(core);
+ if (!pipemux_strap_is_valid(core->pipemux)) {
+ dev_err(core->dev, "invalid PCIe PIPEMUX strap %u\n",
+ core->pipemux);
+ return -EIO;
+ }
+
+ for (phy_idx = 0; phy_idx < SR_NR_PCIE_PHYS; phy_idx++) {
+ struct sr_pcie_phy *p = &core->phys[phy_idx];
+ const struct phy_ops *ops;
+
+ if (phy_idx == SR_PAXC_PHY_IDX)
+ ops = &sr_paxc_phy_ops;
+ else
+ ops = &sr_pcie_phy_ops;
+
+ p->phy = devm_phy_create(dev, NULL, ops);
+ if (IS_ERR(p->phy)) {
+ dev_err(dev, "failed to create PCIe PHY\n");
+ return PTR_ERR(p->phy);
+ }
+
+ p->core = core;
+ p->index = phy_idx;
+ phy_set_drvdata(p->phy, p);
+ }
+
+ dev_set_drvdata(dev, core);
+
+ provider = devm_of_phy_provider_register(dev, sr_pcie_phy_xlate);
+ if (IS_ERR(provider)) {
+ dev_err(dev, "failed to register PHY provider\n");
+ return PTR_ERR(provider);
+ }
+
+ dev_info(dev, "Stingray PCIe PHY driver initialized\n");
+
+ return 0;
+}
+
+static const struct of_device_id sr_pcie_phy_match_table[] = {
+ { .compatible = "brcm,sr-pcie-phy" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sr_pcie_phy_match_table);
+
+static struct platform_driver sr_pcie_phy_driver = {
+ .driver = {
+ .name = "sr-pcie-phy",
+ .of_match_table = sr_pcie_phy_match_table,
+ },
+ .probe = sr_pcie_phy_probe,
+};
+module_platform_driver(sr_pcie_phy_driver);
+
+MODULE_AUTHOR("Ray Jui <ray.jui@broadcom.com>");
+MODULE_DESCRIPTION("Broadcom Stingray PCIe PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/broadcom/phy-brcm-sata.c b/drivers/phy/broadcom/phy-brcm-sata.c
new file mode 100644
index 000000000..8708ea3b4
--- /dev/null
+++ b/drivers/phy/broadcom/phy-brcm-sata.c
@@ -0,0 +1,669 @@
+/*
+ * Broadcom SATA3 AHCI Controller PHY Driver
+ *
+ * Copyright (C) 2016 Broadcom
+ *
+ * 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, 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/delay.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+#define SATA_PCB_BANK_OFFSET 0x23c
+#define SATA_PCB_REG_OFFSET(ofs) ((ofs) * 4)
+
+#define MAX_PORTS 2
+
+/* Register offset between PHYs in PCB space */
+#define SATA_PCB_REG_28NM_SPACE_SIZE 0x1000
+
+/* The older SATA PHY registers duplicated per port registers within the map,
+ * rather than having a separate map per port.
+ */
+#define SATA_PCB_REG_40NM_SPACE_SIZE 0x10
+
+/* Register offset between PHYs in PHY control space */
+#define SATA_PHY_CTRL_REG_28NM_SPACE_SIZE 0x8
+
+enum brcm_sata_phy_version {
+ BRCM_SATA_PHY_STB_28NM,
+ BRCM_SATA_PHY_STB_40NM,
+ BRCM_SATA_PHY_IPROC_NS2,
+ BRCM_SATA_PHY_IPROC_NSP,
+ BRCM_SATA_PHY_IPROC_SR,
+};
+
+enum brcm_sata_phy_rxaeq_mode {
+ RXAEQ_MODE_OFF = 0,
+ RXAEQ_MODE_AUTO,
+ RXAEQ_MODE_MANUAL,
+};
+
+static enum brcm_sata_phy_rxaeq_mode rxaeq_to_val(const char *m)
+{
+ if (!strcmp(m, "auto"))
+ return RXAEQ_MODE_AUTO;
+ else if (!strcmp(m, "manual"))
+ return RXAEQ_MODE_MANUAL;
+ else
+ return RXAEQ_MODE_OFF;
+}
+
+struct brcm_sata_port {
+ int portnum;
+ struct phy *phy;
+ struct brcm_sata_phy *phy_priv;
+ bool ssc_en;
+ enum brcm_sata_phy_rxaeq_mode rxaeq_mode;
+ u32 rxaeq_val;
+};
+
+struct brcm_sata_phy {
+ struct device *dev;
+ void __iomem *phy_base;
+ void __iomem *ctrl_base;
+ enum brcm_sata_phy_version version;
+
+ struct brcm_sata_port phys[MAX_PORTS];
+};
+
+enum sata_phy_regs {
+ BLOCK0_REG_BANK = 0x000,
+ BLOCK0_XGXSSTATUS = 0x81,
+ BLOCK0_XGXSSTATUS_PLL_LOCK = BIT(12),
+ BLOCK0_SPARE = 0x8d,
+ BLOCK0_SPARE_OOB_CLK_SEL_MASK = 0x3,
+ BLOCK0_SPARE_OOB_CLK_SEL_REFBY2 = 0x1,
+
+ PLL_REG_BANK_0 = 0x050,
+ PLL_REG_BANK_0_PLLCONTROL_0 = 0x81,
+ PLLCONTROL_0_FREQ_DET_RESTART = BIT(13),
+ PLLCONTROL_0_FREQ_MONITOR = BIT(12),
+ PLLCONTROL_0_SEQ_START = BIT(15),
+ PLL_CAP_CONTROL = 0x85,
+ PLL_ACTRL2 = 0x8b,
+ PLL_ACTRL2_SELDIV_MASK = 0x1f,
+ PLL_ACTRL2_SELDIV_SHIFT = 9,
+ PLL_ACTRL6 = 0x86,
+
+ PLL1_REG_BANK = 0x060,
+ PLL1_ACTRL2 = 0x82,
+ PLL1_ACTRL3 = 0x83,
+ PLL1_ACTRL4 = 0x84,
+
+ TX_REG_BANK = 0x070,
+ TX_ACTRL0 = 0x80,
+ TX_ACTRL0_TXPOL_FLIP = BIT(6),
+
+ AEQRX_REG_BANK_0 = 0xd0,
+ AEQ_CONTROL1 = 0x81,
+ AEQ_CONTROL1_ENABLE = BIT(2),
+ AEQ_CONTROL1_FREEZE = BIT(3),
+ AEQ_FRC_EQ = 0x83,
+ AEQ_FRC_EQ_FORCE = BIT(0),
+ AEQ_FRC_EQ_FORCE_VAL = BIT(1),
+ AEQRX_REG_BANK_1 = 0xe0,
+
+ OOB_REG_BANK = 0x150,
+ OOB1_REG_BANK = 0x160,
+ OOB_CTRL1 = 0x80,
+ OOB_CTRL1_BURST_MAX_MASK = 0xf,
+ OOB_CTRL1_BURST_MAX_SHIFT = 12,
+ OOB_CTRL1_BURST_MIN_MASK = 0xf,
+ OOB_CTRL1_BURST_MIN_SHIFT = 8,
+ OOB_CTRL1_WAKE_IDLE_MAX_MASK = 0xf,
+ OOB_CTRL1_WAKE_IDLE_MAX_SHIFT = 4,
+ OOB_CTRL1_WAKE_IDLE_MIN_MASK = 0xf,
+ OOB_CTRL1_WAKE_IDLE_MIN_SHIFT = 0,
+ OOB_CTRL2 = 0x81,
+ OOB_CTRL2_SEL_ENA_SHIFT = 15,
+ OOB_CTRL2_SEL_ENA_RC_SHIFT = 14,
+ OOB_CTRL2_RESET_IDLE_MAX_MASK = 0x3f,
+ OOB_CTRL2_RESET_IDLE_MAX_SHIFT = 8,
+ OOB_CTRL2_BURST_CNT_MASK = 0x3,
+ OOB_CTRL2_BURST_CNT_SHIFT = 6,
+ OOB_CTRL2_RESET_IDLE_MIN_MASK = 0x3f,
+ OOB_CTRL2_RESET_IDLE_MIN_SHIFT = 0,
+
+ TXPMD_REG_BANK = 0x1a0,
+ TXPMD_CONTROL1 = 0x81,
+ TXPMD_CONTROL1_TX_SSC_EN_FRC = BIT(0),
+ TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL = BIT(1),
+ TXPMD_TX_FREQ_CTRL_CONTROL1 = 0x82,
+ TXPMD_TX_FREQ_CTRL_CONTROL2 = 0x83,
+ TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK = 0x3ff,
+ TXPMD_TX_FREQ_CTRL_CONTROL3 = 0x84,
+ TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK = 0x3ff,
+
+ RXPMD_REG_BANK = 0x1c0,
+ RXPMD_RX_FREQ_MON_CONTROL1 = 0x87,
+};
+
+enum sata_phy_ctrl_regs {
+ PHY_CTRL_1 = 0x0,
+ PHY_CTRL_1_RESET = BIT(0),
+};
+
+static inline void __iomem *brcm_sata_pcb_base(struct brcm_sata_port *port)
+{
+ struct brcm_sata_phy *priv = port->phy_priv;
+ u32 size = 0;
+
+ switch (priv->version) {
+ case BRCM_SATA_PHY_STB_28NM:
+ case BRCM_SATA_PHY_IPROC_NS2:
+ size = SATA_PCB_REG_28NM_SPACE_SIZE;
+ break;
+ case BRCM_SATA_PHY_STB_40NM:
+ size = SATA_PCB_REG_40NM_SPACE_SIZE;
+ break;
+ default:
+ dev_err(priv->dev, "invalid phy version\n");
+ break;
+ }
+
+ return priv->phy_base + (port->portnum * size);
+}
+
+static inline void __iomem *brcm_sata_ctrl_base(struct brcm_sata_port *port)
+{
+ struct brcm_sata_phy *priv = port->phy_priv;
+ u32 size = 0;
+
+ switch (priv->version) {
+ case BRCM_SATA_PHY_IPROC_NS2:
+ size = SATA_PHY_CTRL_REG_28NM_SPACE_SIZE;
+ break;
+ default:
+ dev_err(priv->dev, "invalid phy version\n");
+ break;
+ }
+
+ return priv->ctrl_base + (port->portnum * size);
+}
+
+static void brcm_sata_phy_wr(void __iomem *pcb_base, u32 bank,
+ u32 ofs, u32 msk, u32 value)
+{
+ u32 tmp;
+
+ writel(bank, pcb_base + SATA_PCB_BANK_OFFSET);
+ tmp = readl(pcb_base + SATA_PCB_REG_OFFSET(ofs));
+ tmp = (tmp & msk) | value;
+ writel(tmp, pcb_base + SATA_PCB_REG_OFFSET(ofs));
+}
+
+static u32 brcm_sata_phy_rd(void __iomem *pcb_base, u32 bank, u32 ofs)
+{
+ writel(bank, pcb_base + SATA_PCB_BANK_OFFSET);
+ return readl(pcb_base + SATA_PCB_REG_OFFSET(ofs));
+}
+
+/* These defaults were characterized by H/W group */
+#define STB_FMIN_VAL_DEFAULT 0x3df
+#define STB_FMAX_VAL_DEFAULT 0x3df
+#define STB_FMAX_VAL_SSC 0x83
+
+static void brcm_stb_sata_ssc_init(struct brcm_sata_port *port)
+{
+ void __iomem *base = brcm_sata_pcb_base(port);
+ struct brcm_sata_phy *priv = port->phy_priv;
+ u32 tmp;
+
+ /* override the TX spread spectrum setting */
+ tmp = TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL | TXPMD_CONTROL1_TX_SSC_EN_FRC;
+ brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_CONTROL1, ~tmp, tmp);
+
+ /* set fixed min freq */
+ brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL2,
+ ~TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK,
+ STB_FMIN_VAL_DEFAULT);
+
+ /* set fixed max freq depending on SSC config */
+ if (port->ssc_en) {
+ dev_info(priv->dev, "enabling SSC on port%d\n", port->portnum);
+ tmp = STB_FMAX_VAL_SSC;
+ } else {
+ tmp = STB_FMAX_VAL_DEFAULT;
+ }
+
+ brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL3,
+ ~TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK, tmp);
+}
+
+#define AEQ_FRC_EQ_VAL_SHIFT 2
+#define AEQ_FRC_EQ_VAL_MASK 0x3f
+
+static int brcm_stb_sata_rxaeq_init(struct brcm_sata_port *port)
+{
+ void __iomem *base = brcm_sata_pcb_base(port);
+ u32 tmp = 0, reg = 0;
+
+ switch (port->rxaeq_mode) {
+ case RXAEQ_MODE_OFF:
+ return 0;
+
+ case RXAEQ_MODE_AUTO:
+ reg = AEQ_CONTROL1;
+ tmp = AEQ_CONTROL1_ENABLE | AEQ_CONTROL1_FREEZE;
+ break;
+
+ case RXAEQ_MODE_MANUAL:
+ reg = AEQ_FRC_EQ;
+ tmp = AEQ_FRC_EQ_FORCE | AEQ_FRC_EQ_FORCE_VAL;
+ if (port->rxaeq_val > AEQ_FRC_EQ_VAL_MASK)
+ return -EINVAL;
+ tmp |= port->rxaeq_val << AEQ_FRC_EQ_VAL_SHIFT;
+ break;
+ }
+
+ brcm_sata_phy_wr(base, AEQRX_REG_BANK_0, reg, ~tmp, tmp);
+ brcm_sata_phy_wr(base, AEQRX_REG_BANK_1, reg, ~tmp, tmp);
+
+ return 0;
+}
+
+static int brcm_stb_sata_init(struct brcm_sata_port *port)
+{
+ brcm_stb_sata_ssc_init(port);
+
+ return brcm_stb_sata_rxaeq_init(port);
+}
+
+/* NS2 SATA PLL1 defaults were characterized by H/W group */
+#define NS2_PLL1_ACTRL2_MAGIC 0x1df8
+#define NS2_PLL1_ACTRL3_MAGIC 0x2b00
+#define NS2_PLL1_ACTRL4_MAGIC 0x8824
+
+static int brcm_ns2_sata_init(struct brcm_sata_port *port)
+{
+ int try;
+ unsigned int val;
+ void __iomem *base = brcm_sata_pcb_base(port);
+ void __iomem *ctrl_base = brcm_sata_ctrl_base(port);
+ struct device *dev = port->phy_priv->dev;
+
+ /* Configure OOB control */
+ val = 0x0;
+ val |= (0xc << OOB_CTRL1_BURST_MAX_SHIFT);
+ val |= (0x4 << OOB_CTRL1_BURST_MIN_SHIFT);
+ val |= (0x9 << OOB_CTRL1_WAKE_IDLE_MAX_SHIFT);
+ val |= (0x3 << OOB_CTRL1_WAKE_IDLE_MIN_SHIFT);
+ brcm_sata_phy_wr(base, OOB_REG_BANK, OOB_CTRL1, 0x0, val);
+ val = 0x0;
+ val |= (0x1b << OOB_CTRL2_RESET_IDLE_MAX_SHIFT);
+ val |= (0x2 << OOB_CTRL2_BURST_CNT_SHIFT);
+ val |= (0x9 << OOB_CTRL2_RESET_IDLE_MIN_SHIFT);
+ brcm_sata_phy_wr(base, OOB_REG_BANK, OOB_CTRL2, 0x0, val);
+
+ /* Configure PHY PLL register bank 1 */
+ val = NS2_PLL1_ACTRL2_MAGIC;
+ brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL2, 0x0, val);
+ val = NS2_PLL1_ACTRL3_MAGIC;
+ brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL3, 0x0, val);
+ val = NS2_PLL1_ACTRL4_MAGIC;
+ brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL4, 0x0, val);
+
+ /* Configure PHY BLOCK0 register bank */
+ /* Set oob_clk_sel to refclk/2 */
+ brcm_sata_phy_wr(base, BLOCK0_REG_BANK, BLOCK0_SPARE,
+ ~BLOCK0_SPARE_OOB_CLK_SEL_MASK,
+ BLOCK0_SPARE_OOB_CLK_SEL_REFBY2);
+
+ /* Strobe PHY reset using PHY control register */
+ writel(PHY_CTRL_1_RESET, ctrl_base + PHY_CTRL_1);
+ mdelay(1);
+ writel(0x0, ctrl_base + PHY_CTRL_1);
+ mdelay(1);
+
+ /* Wait for PHY PLL lock by polling pll_lock bit */
+ try = 50;
+ while (try) {
+ val = brcm_sata_phy_rd(base, BLOCK0_REG_BANK,
+ BLOCK0_XGXSSTATUS);
+ if (val & BLOCK0_XGXSSTATUS_PLL_LOCK)
+ break;
+ msleep(20);
+ try--;
+ }
+ if (!try) {
+ /* PLL did not lock; give up */
+ dev_err(dev, "port%d PLL did not lock\n", port->portnum);
+ return -ETIMEDOUT;
+ }
+
+ dev_dbg(dev, "port%d initialized\n", port->portnum);
+
+ return 0;
+}
+
+static int brcm_nsp_sata_init(struct brcm_sata_port *port)
+{
+ struct brcm_sata_phy *priv = port->phy_priv;
+ struct device *dev = port->phy_priv->dev;
+ void __iomem *base = priv->phy_base;
+ unsigned int oob_bank;
+ unsigned int val, try;
+
+ /* Configure OOB control */
+ if (port->portnum == 0)
+ oob_bank = OOB_REG_BANK;
+ else if (port->portnum == 1)
+ oob_bank = OOB1_REG_BANK;
+ else
+ return -EINVAL;
+
+ val = 0x0;
+ val |= (0x0f << OOB_CTRL1_BURST_MAX_SHIFT);
+ val |= (0x06 << OOB_CTRL1_BURST_MIN_SHIFT);
+ val |= (0x0f << OOB_CTRL1_WAKE_IDLE_MAX_SHIFT);
+ val |= (0x06 << OOB_CTRL1_WAKE_IDLE_MIN_SHIFT);
+ brcm_sata_phy_wr(base, oob_bank, OOB_CTRL1, 0x0, val);
+
+ val = 0x0;
+ val |= (0x2e << OOB_CTRL2_RESET_IDLE_MAX_SHIFT);
+ val |= (0x02 << OOB_CTRL2_BURST_CNT_SHIFT);
+ val |= (0x16 << OOB_CTRL2_RESET_IDLE_MIN_SHIFT);
+ brcm_sata_phy_wr(base, oob_bank, OOB_CTRL2, 0x0, val);
+
+
+ brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_ACTRL2,
+ ~(PLL_ACTRL2_SELDIV_MASK << PLL_ACTRL2_SELDIV_SHIFT),
+ 0x0c << PLL_ACTRL2_SELDIV_SHIFT);
+
+ brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_CAP_CONTROL,
+ 0xff0, 0x4f0);
+
+ val = PLLCONTROL_0_FREQ_DET_RESTART | PLLCONTROL_0_FREQ_MONITOR;
+ brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0,
+ ~val, val);
+ val = PLLCONTROL_0_SEQ_START;
+ brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0,
+ ~val, 0);
+ mdelay(10);
+ brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0,
+ ~val, val);
+
+ /* Wait for pll_seq_done bit */
+ try = 50;
+ while (--try) {
+ val = brcm_sata_phy_rd(base, BLOCK0_REG_BANK,
+ BLOCK0_XGXSSTATUS);
+ if (val & BLOCK0_XGXSSTATUS_PLL_LOCK)
+ break;
+ msleep(20);
+ }
+ if (!try) {
+ /* PLL did not lock; give up */
+ dev_err(dev, "port%d PLL did not lock\n", port->portnum);
+ return -ETIMEDOUT;
+ }
+
+ dev_dbg(dev, "port%d initialized\n", port->portnum);
+
+ return 0;
+}
+
+/* SR PHY PLL0 registers */
+#define SR_PLL0_ACTRL6_MAGIC 0xa
+
+/* SR PHY PLL1 registers */
+#define SR_PLL1_ACTRL2_MAGIC 0x32
+#define SR_PLL1_ACTRL3_MAGIC 0x2
+#define SR_PLL1_ACTRL4_MAGIC 0x3e8
+
+static int brcm_sr_sata_init(struct brcm_sata_port *port)
+{
+ struct brcm_sata_phy *priv = port->phy_priv;
+ struct device *dev = port->phy_priv->dev;
+ void __iomem *base = priv->phy_base;
+ unsigned int val, try;
+
+ /* Configure PHY PLL register bank 1 */
+ val = SR_PLL1_ACTRL2_MAGIC;
+ brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL2, 0x0, val);
+ val = SR_PLL1_ACTRL3_MAGIC;
+ brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL3, 0x0, val);
+ val = SR_PLL1_ACTRL4_MAGIC;
+ brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL4, 0x0, val);
+
+ /* Configure PHY PLL register bank 0 */
+ val = SR_PLL0_ACTRL6_MAGIC;
+ brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_ACTRL6, 0x0, val);
+
+ /* Wait for PHY PLL lock by polling pll_lock bit */
+ try = 50;
+ do {
+ val = brcm_sata_phy_rd(base, BLOCK0_REG_BANK,
+ BLOCK0_XGXSSTATUS);
+ if (val & BLOCK0_XGXSSTATUS_PLL_LOCK)
+ break;
+ msleep(20);
+ try--;
+ } while (try);
+
+ if ((val & BLOCK0_XGXSSTATUS_PLL_LOCK) == 0) {
+ /* PLL did not lock; give up */
+ dev_err(dev, "port%d PLL did not lock\n", port->portnum);
+ return -ETIMEDOUT;
+ }
+
+ /* Invert Tx polarity */
+ brcm_sata_phy_wr(base, TX_REG_BANK, TX_ACTRL0,
+ ~TX_ACTRL0_TXPOL_FLIP, TX_ACTRL0_TXPOL_FLIP);
+
+ /* Configure OOB control to handle 100MHz reference clock */
+ val = ((0xc << OOB_CTRL1_BURST_MAX_SHIFT) |
+ (0x4 << OOB_CTRL1_BURST_MIN_SHIFT) |
+ (0x8 << OOB_CTRL1_WAKE_IDLE_MAX_SHIFT) |
+ (0x3 << OOB_CTRL1_WAKE_IDLE_MIN_SHIFT));
+ brcm_sata_phy_wr(base, OOB_REG_BANK, OOB_CTRL1, 0x0, val);
+ val = ((0x1b << OOB_CTRL2_RESET_IDLE_MAX_SHIFT) |
+ (0x2 << OOB_CTRL2_BURST_CNT_SHIFT) |
+ (0x9 << OOB_CTRL2_RESET_IDLE_MIN_SHIFT));
+ brcm_sata_phy_wr(base, OOB_REG_BANK, OOB_CTRL2, 0x0, val);
+
+ return 0;
+}
+
+static int brcm_sata_phy_init(struct phy *phy)
+{
+ int rc;
+ struct brcm_sata_port *port = phy_get_drvdata(phy);
+
+ switch (port->phy_priv->version) {
+ case BRCM_SATA_PHY_STB_28NM:
+ case BRCM_SATA_PHY_STB_40NM:
+ rc = brcm_stb_sata_init(port);
+ break;
+ case BRCM_SATA_PHY_IPROC_NS2:
+ rc = brcm_ns2_sata_init(port);
+ break;
+ case BRCM_SATA_PHY_IPROC_NSP:
+ rc = brcm_nsp_sata_init(port);
+ break;
+ case BRCM_SATA_PHY_IPROC_SR:
+ rc = brcm_sr_sata_init(port);
+ break;
+ default:
+ rc = -ENODEV;
+ }
+
+ return rc;
+}
+
+static void brcm_stb_sata_calibrate(struct brcm_sata_port *port)
+{
+ void __iomem *base = brcm_sata_pcb_base(port);
+ u32 tmp = BIT(8);
+
+ brcm_sata_phy_wr(base, RXPMD_REG_BANK, RXPMD_RX_FREQ_MON_CONTROL1,
+ ~tmp, tmp);
+}
+
+static int brcm_sata_phy_calibrate(struct phy *phy)
+{
+ struct brcm_sata_port *port = phy_get_drvdata(phy);
+ int rc = -EOPNOTSUPP;
+
+ switch (port->phy_priv->version) {
+ case BRCM_SATA_PHY_STB_28NM:
+ case BRCM_SATA_PHY_STB_40NM:
+ brcm_stb_sata_calibrate(port);
+ rc = 0;
+ break;
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+static const struct phy_ops phy_ops = {
+ .init = brcm_sata_phy_init,
+ .calibrate = brcm_sata_phy_calibrate,
+ .owner = THIS_MODULE,
+};
+
+static const struct of_device_id brcm_sata_phy_of_match[] = {
+ { .compatible = "brcm,bcm7445-sata-phy",
+ .data = (void *)BRCM_SATA_PHY_STB_28NM },
+ { .compatible = "brcm,bcm7425-sata-phy",
+ .data = (void *)BRCM_SATA_PHY_STB_40NM },
+ { .compatible = "brcm,iproc-ns2-sata-phy",
+ .data = (void *)BRCM_SATA_PHY_IPROC_NS2 },
+ { .compatible = "brcm,iproc-nsp-sata-phy",
+ .data = (void *)BRCM_SATA_PHY_IPROC_NSP },
+ { .compatible = "brcm,iproc-sr-sata-phy",
+ .data = (void *)BRCM_SATA_PHY_IPROC_SR },
+ {},
+};
+MODULE_DEVICE_TABLE(of, brcm_sata_phy_of_match);
+
+static int brcm_sata_phy_probe(struct platform_device *pdev)
+{
+ const char *rxaeq_mode;
+ struct device *dev = &pdev->dev;
+ struct device_node *dn = dev->of_node, *child;
+ const struct of_device_id *of_id;
+ struct brcm_sata_phy *priv;
+ struct resource *res;
+ struct phy_provider *provider;
+ int ret, count = 0;
+
+ if (of_get_child_count(dn) == 0)
+ return -ENODEV;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ dev_set_drvdata(dev, priv);
+ priv->dev = dev;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy");
+ priv->phy_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(priv->phy_base))
+ return PTR_ERR(priv->phy_base);
+
+ of_id = of_match_node(brcm_sata_phy_of_match, dn);
+ if (of_id)
+ priv->version = (enum brcm_sata_phy_version)of_id->data;
+ else
+ priv->version = BRCM_SATA_PHY_STB_28NM;
+
+ if (priv->version == BRCM_SATA_PHY_IPROC_NS2) {
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "phy-ctrl");
+ priv->ctrl_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(priv->ctrl_base))
+ return PTR_ERR(priv->ctrl_base);
+ }
+
+ for_each_available_child_of_node(dn, child) {
+ unsigned int id;
+ struct brcm_sata_port *port;
+
+ if (of_property_read_u32(child, "reg", &id)) {
+ dev_err(dev, "missing reg property in node %s\n",
+ child->name);
+ ret = -EINVAL;
+ goto put_child;
+ }
+
+ if (id >= MAX_PORTS) {
+ dev_err(dev, "invalid reg: %u\n", id);
+ ret = -EINVAL;
+ goto put_child;
+ }
+ if (priv->phys[id].phy) {
+ dev_err(dev, "already registered port %u\n", id);
+ ret = -EINVAL;
+ goto put_child;
+ }
+
+ port = &priv->phys[id];
+ port->portnum = id;
+ port->phy_priv = priv;
+ port->phy = devm_phy_create(dev, child, &phy_ops);
+ port->rxaeq_mode = RXAEQ_MODE_OFF;
+ if (!of_property_read_string(child, "brcm,rxaeq-mode",
+ &rxaeq_mode))
+ port->rxaeq_mode = rxaeq_to_val(rxaeq_mode);
+ if (port->rxaeq_mode == RXAEQ_MODE_MANUAL)
+ of_property_read_u32(child, "brcm,rxaeq-value",
+ &port->rxaeq_val);
+ port->ssc_en = of_property_read_bool(child, "brcm,enable-ssc");
+ if (IS_ERR(port->phy)) {
+ dev_err(dev, "failed to create PHY\n");
+ ret = PTR_ERR(port->phy);
+ goto put_child;
+ }
+
+ phy_set_drvdata(port->phy, port);
+ count++;
+ }
+
+ provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(provider)) {
+ dev_err(dev, "could not register PHY provider\n");
+ return PTR_ERR(provider);
+ }
+
+ dev_info(dev, "registered %d port(s)\n", count);
+
+ return 0;
+put_child:
+ of_node_put(child);
+ return ret;
+}
+
+static struct platform_driver brcm_sata_phy_driver = {
+ .probe = brcm_sata_phy_probe,
+ .driver = {
+ .of_match_table = brcm_sata_phy_of_match,
+ .name = "brcm-sata-phy",
+ }
+};
+module_platform_driver(brcm_sata_phy_driver);
+
+MODULE_DESCRIPTION("Broadcom SATA PHY driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Marc Carino");
+MODULE_AUTHOR("Brian Norris");
+MODULE_ALIAS("platform:phy-brcm-sata");
diff --git a/drivers/phy/broadcom/phy-brcm-usb-init.c b/drivers/phy/broadcom/phy-brcm-usb-init.c
new file mode 100644
index 000000000..29d2c3b19
--- /dev/null
+++ b/drivers/phy/broadcom/phy-brcm-usb-init.c
@@ -0,0 +1,1019 @@
+/*
+ * phy-brcm-usb-init.c - Broadcom USB Phy chip specific init functions
+ *
+ * Copyright (C) 2014-2017 Broadcom
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * This module contains USB PHY initialization for power up and S3 resume
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+
+#include <linux/soc/brcmstb/brcmstb.h>
+#include "phy-brcm-usb-init.h"
+
+#define PHY_PORTS 2
+#define PHY_PORT_SELECT_0 0
+#define PHY_PORT_SELECT_1 0x1000
+
+/* Register definitions for the USB CTRL block */
+#define USB_CTRL_SETUP 0x00
+#define USB_CTRL_SETUP_IOC_MASK 0x00000010
+#define USB_CTRL_SETUP_IPP_MASK 0x00000020
+#define USB_CTRL_SETUP_BABO_MASK 0x00000001
+#define USB_CTRL_SETUP_FNHW_MASK 0x00000002
+#define USB_CTRL_SETUP_FNBO_MASK 0x00000004
+#define USB_CTRL_SETUP_WABO_MASK 0x00000008
+#define USB_CTRL_SETUP_SCB_CLIENT_SWAP_MASK 0x00002000 /* option */
+#define USB_CTRL_SETUP_SCB1_EN_MASK 0x00004000 /* option */
+#define USB_CTRL_SETUP_SCB2_EN_MASK 0x00008000 /* option */
+#define USB_CTRL_SETUP_SS_EHCI64BIT_EN_MASK 0X00020000 /* option */
+#define USB_CTRL_SETUP_SS_EHCI64BIT_EN_VAR_MASK 0x00010000 /* option */
+#define USB_CTRL_SETUP_STRAP_IPP_SEL_MASK 0x02000000 /* option */
+#define USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK 0x04000000 /* option */
+#define USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK 0x08000000 /* opt */
+#define USB_CTRL_SETUP_OC3_DISABLE_MASK 0xc0000000 /* option */
+#define USB_CTRL_PLL_CTL 0x04
+#define USB_CTRL_PLL_CTL_PLL_SUSPEND_EN_MASK 0x08000000
+#define USB_CTRL_PLL_CTL_PLL_RESETB_MASK 0x40000000
+#define USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK 0x80000000 /* option */
+#define USB_CTRL_EBRIDGE 0x0c
+#define USB_CTRL_EBRIDGE_ESTOP_SCB_REQ_MASK 0x00020000 /* option */
+#define USB_CTRL_OBRIDGE 0x10
+#define USB_CTRL_OBRIDGE_LS_KEEP_ALIVE_MASK 0x08000000
+#define USB_CTRL_MDIO 0x14
+#define USB_CTRL_MDIO2 0x18
+#define USB_CTRL_UTMI_CTL_1 0x2c
+#define USB_CTRL_UTMI_CTL_1_POWER_UP_FSM_EN_MASK 0x00000800
+#define USB_CTRL_UTMI_CTL_1_POWER_UP_FSM_EN_P1_MASK 0x08000000
+#define USB_CTRL_USB_PM 0x34
+#define USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK 0x00800000 /* option */
+#define USB_CTRL_USB_PM_XHC_SOFT_RESETB_MASK 0x00400000 /* option */
+#define USB_CTRL_USB_PM_XHC_SOFT_RESETB_VAR_MASK 0x40000000 /* option */
+#define USB_CTRL_USB_PM_USB_PWRDN_MASK 0x80000000 /* option */
+#define USB_CTRL_USB_PM_SOFT_RESET_MASK 0x40000000 /* option */
+#define USB_CTRL_USB_PM_USB20_HC_RESETB_MASK 0x30000000 /* option */
+#define USB_CTRL_USB_PM_USB20_HC_RESETB_VAR_MASK 0x00300000 /* option */
+#define USB_CTRL_USB30_CTL1 0x60
+#define USB_CTRL_USB30_CTL1_PHY3_PLL_SEQ_START_MASK 0x00000010
+#define USB_CTRL_USB30_CTL1_PHY3_RESETB_MASK 0x00010000
+#define USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK 0x00020000 /* option */
+#define USB_CTRL_USB30_CTL1_USB3_IOC_MASK 0x10000000 /* option */
+#define USB_CTRL_USB30_CTL1_USB3_IPP_MASK 0x20000000 /* option */
+#define USB_CTRL_USB30_PCTL 0x70
+#define USB_CTRL_USB30_PCTL_PHY3_SOFT_RESETB_MASK 0x00000002
+#define USB_CTRL_USB30_PCTL_PHY3_IDDQ_OVERRIDE_MASK 0x00008000
+#define USB_CTRL_USB30_PCTL_PHY3_SOFT_RESETB_P1_MASK 0x00020000
+#define USB_CTRL_USB_DEVICE_CTL1 0x90
+#define USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK 0x00000003 /* option */
+
+/* Register definitions for the XHCI EC block */
+#define USB_XHCI_EC_IRAADR 0x658
+#define USB_XHCI_EC_IRADAT 0x65c
+
+enum brcm_family_type {
+ BRCM_FAMILY_3390A0,
+ BRCM_FAMILY_7250B0,
+ BRCM_FAMILY_7271A0,
+ BRCM_FAMILY_7364A0,
+ BRCM_FAMILY_7366C0,
+ BRCM_FAMILY_74371A0,
+ BRCM_FAMILY_7439B0,
+ BRCM_FAMILY_7445D0,
+ BRCM_FAMILY_7260A0,
+ BRCM_FAMILY_7278A0,
+ BRCM_FAMILY_COUNT,
+};
+
+#define USB_BRCM_FAMILY(chip) \
+ [BRCM_FAMILY_##chip] = __stringify(chip)
+
+static const char *family_names[BRCM_FAMILY_COUNT] = {
+ USB_BRCM_FAMILY(3390A0),
+ USB_BRCM_FAMILY(7250B0),
+ USB_BRCM_FAMILY(7271A0),
+ USB_BRCM_FAMILY(7364A0),
+ USB_BRCM_FAMILY(7366C0),
+ USB_BRCM_FAMILY(74371A0),
+ USB_BRCM_FAMILY(7439B0),
+ USB_BRCM_FAMILY(7445D0),
+ USB_BRCM_FAMILY(7260A0),
+ USB_BRCM_FAMILY(7278A0),
+};
+
+enum {
+ USB_CTRL_SETUP_SCB1_EN_SELECTOR,
+ USB_CTRL_SETUP_SCB2_EN_SELECTOR,
+ USB_CTRL_SETUP_SS_EHCI64BIT_EN_SELECTOR,
+ USB_CTRL_SETUP_STRAP_IPP_SEL_SELECTOR,
+ USB_CTRL_SETUP_OC3_DISABLE_SELECTOR,
+ USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_SELECTOR,
+ USB_CTRL_USB_PM_BDC_SOFT_RESETB_SELECTOR,
+ USB_CTRL_USB_PM_XHC_SOFT_RESETB_SELECTOR,
+ USB_CTRL_USB_PM_USB_PWRDN_SELECTOR,
+ USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_SELECTOR,
+ USB_CTRL_USB30_CTL1_USB3_IOC_SELECTOR,
+ USB_CTRL_USB30_CTL1_USB3_IPP_SELECTOR,
+ USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_SELECTOR,
+ USB_CTRL_USB_PM_SOFT_RESET_SELECTOR,
+ USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_SELECTOR,
+ USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_SELECTOR,
+ USB_CTRL_USB_PM_USB20_HC_RESETB_SELECTOR,
+ USB_CTRL_SETUP_ENDIAN_SELECTOR,
+ USB_CTRL_SELECTOR_COUNT,
+};
+
+#define USB_CTRL_REG(base, reg) ((void *)base + USB_CTRL_##reg)
+#define USB_XHCI_EC_REG(base, reg) ((void *)base + USB_XHCI_EC_##reg)
+#define USB_CTRL_MASK(reg, field) \
+ USB_CTRL_##reg##_##field##_MASK
+#define USB_CTRL_MASK_FAMILY(params, reg, field) \
+ (params->usb_reg_bits_map[USB_CTRL_##reg##_##field##_SELECTOR])
+
+#define USB_CTRL_SET_FAMILY(params, reg, field) \
+ usb_ctrl_set_family(params, USB_CTRL_##reg, \
+ USB_CTRL_##reg##_##field##_SELECTOR)
+#define USB_CTRL_UNSET_FAMILY(params, reg, field) \
+ usb_ctrl_unset_family(params, USB_CTRL_##reg, \
+ USB_CTRL_##reg##_##field##_SELECTOR)
+
+#define USB_CTRL_SET(base, reg, field) \
+ usb_ctrl_set(USB_CTRL_REG(base, reg), \
+ USB_CTRL_##reg##_##field##_MASK)
+#define USB_CTRL_UNSET(base, reg, field) \
+ usb_ctrl_unset(USB_CTRL_REG(base, reg), \
+ USB_CTRL_##reg##_##field##_MASK)
+
+#define MDIO_USB2 0
+#define MDIO_USB3 BIT(31)
+
+#define USB_CTRL_SETUP_ENDIAN_BITS ( \
+ USB_CTRL_MASK(SETUP, BABO) | \
+ USB_CTRL_MASK(SETUP, FNHW) | \
+ USB_CTRL_MASK(SETUP, FNBO) | \
+ USB_CTRL_MASK(SETUP, WABO))
+
+#ifdef __LITTLE_ENDIAN
+#define ENDIAN_SETTINGS ( \
+ USB_CTRL_MASK(SETUP, BABO) | \
+ USB_CTRL_MASK(SETUP, FNHW))
+#else
+#define ENDIAN_SETTINGS ( \
+ USB_CTRL_MASK(SETUP, FNHW) | \
+ USB_CTRL_MASK(SETUP, FNBO) | \
+ USB_CTRL_MASK(SETUP, WABO))
+#endif
+
+struct id_to_type {
+ u32 id;
+ int type;
+};
+
+static const struct id_to_type id_to_type_table[] = {
+ { 0x33900000, BRCM_FAMILY_3390A0 },
+ { 0x72500010, BRCM_FAMILY_7250B0 },
+ { 0x72600000, BRCM_FAMILY_7260A0 },
+ { 0x72680000, BRCM_FAMILY_7271A0 },
+ { 0x72710000, BRCM_FAMILY_7271A0 },
+ { 0x73640000, BRCM_FAMILY_7364A0 },
+ { 0x73660020, BRCM_FAMILY_7366C0 },
+ { 0x07437100, BRCM_FAMILY_74371A0 },
+ { 0x74390010, BRCM_FAMILY_7439B0 },
+ { 0x74450030, BRCM_FAMILY_7445D0 },
+ { 0x72780000, BRCM_FAMILY_7278A0 },
+ { 0, BRCM_FAMILY_7271A0 }, /* default */
+};
+
+static const u32
+usb_reg_bits_map_table[BRCM_FAMILY_COUNT][USB_CTRL_SELECTOR_COUNT] = {
+ /* 3390B0 */
+ [BRCM_FAMILY_3390A0] = {
+ USB_CTRL_SETUP_SCB1_EN_MASK,
+ USB_CTRL_SETUP_SCB2_EN_MASK,
+ USB_CTRL_SETUP_SS_EHCI64BIT_EN_MASK,
+ USB_CTRL_SETUP_STRAP_IPP_SEL_MASK,
+ USB_CTRL_SETUP_OC3_DISABLE_MASK,
+ 0, /* USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK */
+ 0, /* USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK */
+ USB_CTRL_USB_PM_XHC_SOFT_RESETB_MASK,
+ USB_CTRL_USB_PM_USB_PWRDN_MASK,
+ 0, /* USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_USB3_IOC_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_USB3_IPP_MASK */
+ USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK,
+ 0, /* USB_CTRL_USB_PM_SOFT_RESET_MASK */
+ 0, /* USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK */
+ 0, /* USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK */
+ USB_CTRL_USB_PM_USB20_HC_RESETB_VAR_MASK,
+ ENDIAN_SETTINGS, /* USB_CTRL_SETUP ENDIAN bits */
+ },
+ /* 7250b0 */
+ [BRCM_FAMILY_7250B0] = {
+ USB_CTRL_SETUP_SCB1_EN_MASK,
+ USB_CTRL_SETUP_SCB2_EN_MASK,
+ USB_CTRL_SETUP_SS_EHCI64BIT_EN_MASK,
+ 0, /* USB_CTRL_SETUP_STRAP_IPP_SEL_MASK */
+ USB_CTRL_SETUP_OC3_DISABLE_MASK,
+ USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK,
+ 0, /* USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK */
+ USB_CTRL_USB_PM_XHC_SOFT_RESETB_VAR_MASK,
+ 0, /* USB_CTRL_USB_PM_USB_PWRDN_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_USB3_IOC_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_USB3_IPP_MASK */
+ 0, /* USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK */
+ 0, /* USB_CTRL_USB_PM_SOFT_RESET_MASK */
+ 0, /* USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK */
+ 0, /* USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK */
+ USB_CTRL_USB_PM_USB20_HC_RESETB_MASK,
+ ENDIAN_SETTINGS, /* USB_CTRL_SETUP ENDIAN bits */
+ },
+ /* 7271a0 */
+ [BRCM_FAMILY_7271A0] = {
+ 0, /* USB_CTRL_SETUP_SCB1_EN_MASK */
+ 0, /* USB_CTRL_SETUP_SCB2_EN_MASK */
+ USB_CTRL_SETUP_SS_EHCI64BIT_EN_MASK,
+ USB_CTRL_SETUP_STRAP_IPP_SEL_MASK,
+ USB_CTRL_SETUP_OC3_DISABLE_MASK,
+ 0, /* USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK */
+ USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK,
+ USB_CTRL_USB_PM_XHC_SOFT_RESETB_MASK,
+ USB_CTRL_USB_PM_USB_PWRDN_MASK,
+ 0, /* USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_USB3_IOC_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_USB3_IPP_MASK */
+ USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK,
+ USB_CTRL_USB_PM_SOFT_RESET_MASK,
+ USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK,
+ USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK,
+ USB_CTRL_USB_PM_USB20_HC_RESETB_VAR_MASK,
+ ENDIAN_SETTINGS, /* USB_CTRL_SETUP ENDIAN bits */
+ },
+ /* 7364a0 */
+ [BRCM_FAMILY_7364A0] = {
+ USB_CTRL_SETUP_SCB1_EN_MASK,
+ USB_CTRL_SETUP_SCB2_EN_MASK,
+ USB_CTRL_SETUP_SS_EHCI64BIT_EN_MASK,
+ 0, /* USB_CTRL_SETUP_STRAP_IPP_SEL_MASK */
+ USB_CTRL_SETUP_OC3_DISABLE_MASK,
+ USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK,
+ 0, /* USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK */
+ USB_CTRL_USB_PM_XHC_SOFT_RESETB_VAR_MASK,
+ 0, /* USB_CTRL_USB_PM_USB_PWRDN_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_USB3_IOC_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_USB3_IPP_MASK */
+ 0, /* USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK */
+ 0, /* USB_CTRL_USB_PM_SOFT_RESET_MASK */
+ 0, /* USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK */
+ 0, /* USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK */
+ USB_CTRL_USB_PM_USB20_HC_RESETB_MASK,
+ ENDIAN_SETTINGS, /* USB_CTRL_SETUP ENDIAN bits */
+ },
+ /* 7366c0 */
+ [BRCM_FAMILY_7366C0] = {
+ USB_CTRL_SETUP_SCB1_EN_MASK,
+ USB_CTRL_SETUP_SCB2_EN_MASK,
+ USB_CTRL_SETUP_SS_EHCI64BIT_EN_MASK,
+ 0, /* USB_CTRL_SETUP_STRAP_IPP_SEL_MASK */
+ USB_CTRL_SETUP_OC3_DISABLE_MASK,
+ 0, /* USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK */
+ 0, /* USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK */
+ USB_CTRL_USB_PM_XHC_SOFT_RESETB_VAR_MASK,
+ USB_CTRL_USB_PM_USB_PWRDN_MASK,
+ 0, /* USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_USB3_IOC_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_USB3_IPP_MASK */
+ 0, /* USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK */
+ 0, /* USB_CTRL_USB_PM_SOFT_RESET_MASK */
+ 0, /* USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK */
+ 0, /* USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK */
+ USB_CTRL_USB_PM_USB20_HC_RESETB_MASK,
+ ENDIAN_SETTINGS, /* USB_CTRL_SETUP ENDIAN bits */
+ },
+ /* 74371A0 */
+ [BRCM_FAMILY_74371A0] = {
+ USB_CTRL_SETUP_SCB1_EN_MASK,
+ USB_CTRL_SETUP_SCB2_EN_MASK,
+ USB_CTRL_SETUP_SS_EHCI64BIT_EN_VAR_MASK,
+ 0, /* USB_CTRL_SETUP_STRAP_IPP_SEL_MASK */
+ 0, /* USB_CTRL_SETUP_OC3_DISABLE_MASK */
+ USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK,
+ 0, /* USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK */
+ 0, /* USB_CTRL_USB_PM_XHC_SOFT_RESETB_MASK */
+ 0, /* USB_CTRL_USB_PM_USB_PWRDN_MASK */
+ USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK,
+ USB_CTRL_USB30_CTL1_USB3_IOC_MASK,
+ USB_CTRL_USB30_CTL1_USB3_IPP_MASK,
+ 0, /* USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK */
+ 0, /* USB_CTRL_USB_PM_SOFT_RESET_MASK */
+ 0, /* USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK */
+ 0, /* USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK */
+ 0, /* USB_CTRL_USB_PM_USB20_HC_RESETB_MASK */
+ ENDIAN_SETTINGS, /* USB_CTRL_SETUP ENDIAN bits */
+ },
+ /* 7439B0 */
+ [BRCM_FAMILY_7439B0] = {
+ USB_CTRL_SETUP_SCB1_EN_MASK,
+ USB_CTRL_SETUP_SCB2_EN_MASK,
+ USB_CTRL_SETUP_SS_EHCI64BIT_EN_MASK,
+ USB_CTRL_SETUP_STRAP_IPP_SEL_MASK,
+ USB_CTRL_SETUP_OC3_DISABLE_MASK,
+ 0, /* USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK */
+ USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK,
+ USB_CTRL_USB_PM_XHC_SOFT_RESETB_MASK,
+ USB_CTRL_USB_PM_USB_PWRDN_MASK,
+ 0, /* USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_USB3_IOC_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_USB3_IPP_MASK */
+ USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK,
+ 0, /* USB_CTRL_USB_PM_SOFT_RESET_MASK */
+ 0, /* USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK */
+ 0, /* USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK */
+ USB_CTRL_USB_PM_USB20_HC_RESETB_VAR_MASK,
+ ENDIAN_SETTINGS, /* USB_CTRL_SETUP ENDIAN bits */
+ },
+ /* 7445d0 */
+ [BRCM_FAMILY_7445D0] = {
+ USB_CTRL_SETUP_SCB1_EN_MASK,
+ USB_CTRL_SETUP_SCB2_EN_MASK,
+ USB_CTRL_SETUP_SS_EHCI64BIT_EN_VAR_MASK,
+ 0, /* USB_CTRL_SETUP_STRAP_IPP_SEL_MASK */
+ USB_CTRL_SETUP_OC3_DISABLE_MASK,
+ USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK,
+ 0, /* USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK */
+ 0, /* USB_CTRL_USB_PM_XHC_SOFT_RESETB_MASK */
+ 0, /* USB_CTRL_USB_PM_USB_PWRDN_MASK */
+ USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK,
+ 0, /* USB_CTRL_USB30_CTL1_USB3_IOC_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_USB3_IPP_MASK */
+ 0, /* USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK */
+ 0, /* USB_CTRL_USB_PM_SOFT_RESET_MASK */
+ 0, /* USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK */
+ 0, /* USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK */
+ USB_CTRL_USB_PM_USB20_HC_RESETB_VAR_MASK,
+ ENDIAN_SETTINGS, /* USB_CTRL_SETUP ENDIAN bits */
+ },
+ /* 7260a0 */
+ [BRCM_FAMILY_7260A0] = {
+ 0, /* USB_CTRL_SETUP_SCB1_EN_MASK */
+ 0, /* USB_CTRL_SETUP_SCB2_EN_MASK */
+ USB_CTRL_SETUP_SS_EHCI64BIT_EN_MASK,
+ USB_CTRL_SETUP_STRAP_IPP_SEL_MASK,
+ USB_CTRL_SETUP_OC3_DISABLE_MASK,
+ 0, /* USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK */
+ USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK,
+ USB_CTRL_USB_PM_XHC_SOFT_RESETB_MASK,
+ USB_CTRL_USB_PM_USB_PWRDN_MASK,
+ 0, /* USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_USB3_IOC_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_USB3_IPP_MASK */
+ USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK,
+ USB_CTRL_USB_PM_SOFT_RESET_MASK,
+ USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK,
+ USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK,
+ USB_CTRL_USB_PM_USB20_HC_RESETB_VAR_MASK,
+ ENDIAN_SETTINGS, /* USB_CTRL_SETUP ENDIAN bits */
+ },
+ /* 7278a0 */
+ [BRCM_FAMILY_7278A0] = {
+ 0, /* USB_CTRL_SETUP_SCB1_EN_MASK */
+ 0, /* USB_CTRL_SETUP_SCB2_EN_MASK */
+ 0, /*USB_CTRL_SETUP_SS_EHCI64BIT_EN_MASK */
+ USB_CTRL_SETUP_STRAP_IPP_SEL_MASK,
+ USB_CTRL_SETUP_OC3_DISABLE_MASK,
+ 0, /* USB_CTRL_PLL_CTL_PLL_IDDQ_PWRDN_MASK */
+ USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK,
+ USB_CTRL_USB_PM_XHC_SOFT_RESETB_MASK,
+ USB_CTRL_USB_PM_USB_PWRDN_MASK,
+ 0, /* USB_CTRL_USB30_CTL1_XHC_SOFT_RESETB_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_USB3_IOC_MASK */
+ 0, /* USB_CTRL_USB30_CTL1_USB3_IPP_MASK */
+ USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK,
+ USB_CTRL_USB_PM_SOFT_RESET_MASK,
+ 0, /* USB_CTRL_SETUP_CC_DRD_MODE_ENABLE_MASK */
+ 0, /* USB_CTRL_SETUP_STRAP_CC_DRD_MODE_ENABLE_SEL_MASK */
+ 0, /* USB_CTRL_USB_PM_USB20_HC_RESETB_MASK */
+ 0, /* USB_CTRL_SETUP ENDIAN bits */
+ },
+};
+
+static inline u32 brcmusb_readl(void __iomem *addr)
+{
+ return readl(addr);
+}
+
+static inline void brcmusb_writel(u32 val, void __iomem *addr)
+{
+ writel(val, addr);
+}
+
+static inline
+void usb_ctrl_unset_family(struct brcm_usb_init_params *params,
+ u32 reg_offset, u32 field)
+{
+ u32 mask;
+ void *reg;
+
+ mask = params->usb_reg_bits_map[field];
+ reg = params->ctrl_regs + reg_offset;
+ brcmusb_writel(brcmusb_readl(reg) & ~mask, reg);
+};
+
+static inline
+void usb_ctrl_set_family(struct brcm_usb_init_params *params,
+ u32 reg_offset, u32 field)
+{
+ u32 mask;
+ void *reg;
+
+ mask = params->usb_reg_bits_map[field];
+ reg = params->ctrl_regs + reg_offset;
+ brcmusb_writel(brcmusb_readl(reg) | mask, reg);
+};
+
+static inline void usb_ctrl_set(void __iomem *reg, u32 field)
+{
+ u32 value;
+
+ value = brcmusb_readl(reg);
+ brcmusb_writel(value | field, reg);
+}
+
+static inline void usb_ctrl_unset(void __iomem *reg, u32 field)
+{
+ u32 value;
+
+ value = brcmusb_readl(reg);
+ brcmusb_writel(value & ~field, reg);
+}
+
+static u32 brcmusb_usb_mdio_read(void __iomem *ctrl_base, u32 reg, int mode)
+{
+ u32 data;
+
+ data = (reg << 16) | mode;
+ brcmusb_writel(data, USB_CTRL_REG(ctrl_base, MDIO));
+ data |= (1 << 24);
+ brcmusb_writel(data, USB_CTRL_REG(ctrl_base, MDIO));
+ data &= ~(1 << 24);
+ /* wait for the 60MHz parallel to serial shifter */
+ usleep_range(10, 20);
+ brcmusb_writel(data, USB_CTRL_REG(ctrl_base, MDIO));
+ /* wait for the 60MHz parallel to serial shifter */
+ usleep_range(10, 20);
+
+ return brcmusb_readl(USB_CTRL_REG(ctrl_base, MDIO2)) & 0xffff;
+}
+
+static void brcmusb_usb_mdio_write(void __iomem *ctrl_base, u32 reg,
+ u32 val, int mode)
+{
+ u32 data;
+
+ data = (reg << 16) | val | mode;
+ brcmusb_writel(data, USB_CTRL_REG(ctrl_base, MDIO));
+ data |= (1 << 25);
+ brcmusb_writel(data, USB_CTRL_REG(ctrl_base, MDIO));
+ data &= ~(1 << 25);
+
+ /* wait for the 60MHz parallel to serial shifter */
+ usleep_range(10, 20);
+ brcmusb_writel(data, USB_CTRL_REG(ctrl_base, MDIO));
+ /* wait for the 60MHz parallel to serial shifter */
+ usleep_range(10, 20);
+}
+
+static void brcmusb_usb_phy_ldo_fix(void __iomem *ctrl_base)
+{
+ /* first disable FSM but also leave it that way */
+ /* to allow normal suspend/resume */
+ USB_CTRL_UNSET(ctrl_base, UTMI_CTL_1, POWER_UP_FSM_EN);
+ USB_CTRL_UNSET(ctrl_base, UTMI_CTL_1, POWER_UP_FSM_EN_P1);
+
+ /* reset USB 2.0 PLL */
+ USB_CTRL_UNSET(ctrl_base, PLL_CTL, PLL_RESETB);
+ /* PLL reset period */
+ udelay(1);
+ USB_CTRL_SET(ctrl_base, PLL_CTL, PLL_RESETB);
+ /* Give PLL enough time to lock */
+ usleep_range(1000, 2000);
+}
+
+static void brcmusb_usb2_eye_fix(void __iomem *ctrl_base)
+{
+ /* Increase USB 2.0 TX level to meet spec requirement */
+ brcmusb_usb_mdio_write(ctrl_base, 0x1f, 0x80a0, MDIO_USB2);
+ brcmusb_usb_mdio_write(ctrl_base, 0x0a, 0xc6a0, MDIO_USB2);
+}
+
+static void brcmusb_usb3_pll_fix(void __iomem *ctrl_base)
+{
+ /* Set correct window for PLL lock detect */
+ brcmusb_usb_mdio_write(ctrl_base, 0x1f, 0x8000, MDIO_USB3);
+ brcmusb_usb_mdio_write(ctrl_base, 0x07, 0x1503, MDIO_USB3);
+}
+
+static void brcmusb_usb3_enable_pipe_reset(void __iomem *ctrl_base)
+{
+ u32 val;
+
+ /* Re-enable USB 3.0 pipe reset */
+ brcmusb_usb_mdio_write(ctrl_base, 0x1f, 0x8000, MDIO_USB3);
+ val = brcmusb_usb_mdio_read(ctrl_base, 0x0f, MDIO_USB3) | 0x200;
+ brcmusb_usb_mdio_write(ctrl_base, 0x0f, val, MDIO_USB3);
+}
+
+static void brcmusb_usb3_enable_sigdet(void __iomem *ctrl_base)
+{
+ u32 val, ofs;
+ int ii;
+
+ ofs = 0;
+ for (ii = 0; ii < PHY_PORTS; ++ii) {
+ /* Set correct default for sigdet */
+ brcmusb_usb_mdio_write(ctrl_base, 0x1f, (0x8080 + ofs),
+ MDIO_USB3);
+ val = brcmusb_usb_mdio_read(ctrl_base, 0x05, MDIO_USB3);
+ val = (val & ~0x800f) | 0x800d;
+ brcmusb_usb_mdio_write(ctrl_base, 0x05, val, MDIO_USB3);
+ ofs = PHY_PORT_SELECT_1;
+ }
+}
+
+static void brcmusb_usb3_enable_skip_align(void __iomem *ctrl_base)
+{
+ u32 val, ofs;
+ int ii;
+
+ ofs = 0;
+ for (ii = 0; ii < PHY_PORTS; ++ii) {
+ /* Set correct default for SKIP align */
+ brcmusb_usb_mdio_write(ctrl_base, 0x1f, (0x8060 + ofs),
+ MDIO_USB3);
+ val = brcmusb_usb_mdio_read(ctrl_base, 0x01, MDIO_USB3) | 0x200;
+ brcmusb_usb_mdio_write(ctrl_base, 0x01, val, MDIO_USB3);
+ ofs = PHY_PORT_SELECT_1;
+ }
+}
+
+static void brcmusb_usb3_unfreeze_aeq(void __iomem *ctrl_base)
+{
+ u32 val, ofs;
+ int ii;
+
+ ofs = 0;
+ for (ii = 0; ii < PHY_PORTS; ++ii) {
+ /* Let EQ freeze after TSEQ */
+ brcmusb_usb_mdio_write(ctrl_base, 0x1f, (0x80e0 + ofs),
+ MDIO_USB3);
+ val = brcmusb_usb_mdio_read(ctrl_base, 0x01, MDIO_USB3);
+ val &= ~0x0008;
+ brcmusb_usb_mdio_write(ctrl_base, 0x01, val, MDIO_USB3);
+ ofs = PHY_PORT_SELECT_1;
+ }
+}
+
+static void brcmusb_usb3_pll_54mhz(struct brcm_usb_init_params *params)
+{
+ u32 ofs;
+ int ii;
+ void __iomem *ctrl_base = params->ctrl_regs;
+
+ /*
+ * On newer B53 based SoC's, the reference clock for the
+ * 3.0 PLL has been changed from 50MHz to 54MHz so the
+ * PLL needs to be reprogrammed.
+ * See SWLINUX-4006.
+ *
+ * On the 7364C0, the reference clock for the
+ * 3.0 PLL has been changed from 50MHz to 54MHz to
+ * work around a MOCA issue.
+ * See SWLINUX-4169.
+ */
+ switch (params->selected_family) {
+ case BRCM_FAMILY_3390A0:
+ case BRCM_FAMILY_7250B0:
+ case BRCM_FAMILY_7366C0:
+ case BRCM_FAMILY_74371A0:
+ case BRCM_FAMILY_7439B0:
+ case BRCM_FAMILY_7445D0:
+ case BRCM_FAMILY_7260A0:
+ return;
+ case BRCM_FAMILY_7364A0:
+ if (BRCM_REV(params->family_id) < 0x20)
+ return;
+ break;
+ }
+
+ /* set USB 3.0 PLL to accept 54Mhz reference clock */
+ USB_CTRL_UNSET(ctrl_base, USB30_CTL1, PHY3_PLL_SEQ_START);
+
+ brcmusb_usb_mdio_write(ctrl_base, 0x1f, 0x8000, MDIO_USB3);
+ brcmusb_usb_mdio_write(ctrl_base, 0x10, 0x5784, MDIO_USB3);
+ brcmusb_usb_mdio_write(ctrl_base, 0x11, 0x01d0, MDIO_USB3);
+ brcmusb_usb_mdio_write(ctrl_base, 0x12, 0x1DE8, MDIO_USB3);
+ brcmusb_usb_mdio_write(ctrl_base, 0x13, 0xAA80, MDIO_USB3);
+ brcmusb_usb_mdio_write(ctrl_base, 0x14, 0x8826, MDIO_USB3);
+ brcmusb_usb_mdio_write(ctrl_base, 0x15, 0x0044, MDIO_USB3);
+ brcmusb_usb_mdio_write(ctrl_base, 0x16, 0x8000, MDIO_USB3);
+ brcmusb_usb_mdio_write(ctrl_base, 0x17, 0x0851, MDIO_USB3);
+ brcmusb_usb_mdio_write(ctrl_base, 0x18, 0x0000, MDIO_USB3);
+
+ /* both ports */
+ ofs = 0;
+ for (ii = 0; ii < PHY_PORTS; ++ii) {
+ brcmusb_usb_mdio_write(ctrl_base, 0x1f, (0x8040 + ofs),
+ MDIO_USB3);
+ brcmusb_usb_mdio_write(ctrl_base, 0x03, 0x0090, MDIO_USB3);
+ brcmusb_usb_mdio_write(ctrl_base, 0x04, 0x0134, MDIO_USB3);
+ brcmusb_usb_mdio_write(ctrl_base, 0x1f, (0x8020 + ofs),
+ MDIO_USB3);
+ brcmusb_usb_mdio_write(ctrl_base, 0x01, 0x00e2, MDIO_USB3);
+ ofs = PHY_PORT_SELECT_1;
+ }
+
+ /* restart PLL sequence */
+ USB_CTRL_SET(ctrl_base, USB30_CTL1, PHY3_PLL_SEQ_START);
+ /* Give PLL enough time to lock */
+ usleep_range(1000, 2000);
+}
+
+static void brcmusb_usb3_ssc_enable(void __iomem *ctrl_base)
+{
+ u32 val;
+
+ /* Enable USB 3.0 TX spread spectrum */
+ brcmusb_usb_mdio_write(ctrl_base, 0x1f, 0x8040, MDIO_USB3);
+ val = brcmusb_usb_mdio_read(ctrl_base, 0x01, MDIO_USB3) | 0xf;
+ brcmusb_usb_mdio_write(ctrl_base, 0x01, val, MDIO_USB3);
+
+ /* Currently, USB 3.0 SSC is enabled via port 0 MDIO registers,
+ * which should have been adequate. However, due to a bug in the
+ * USB 3.0 PHY, it must be enabled via both ports (HWUSB3DVT-26).
+ */
+ brcmusb_usb_mdio_write(ctrl_base, 0x1f, 0x9040, MDIO_USB3);
+ val = brcmusb_usb_mdio_read(ctrl_base, 0x01, MDIO_USB3) | 0xf;
+ brcmusb_usb_mdio_write(ctrl_base, 0x01, val, MDIO_USB3);
+}
+
+static void brcmusb_usb3_phy_workarounds(struct brcm_usb_init_params *params)
+{
+ void __iomem *ctrl_base = params->ctrl_regs;
+
+ brcmusb_usb3_pll_fix(ctrl_base);
+ brcmusb_usb3_pll_54mhz(params);
+ brcmusb_usb3_ssc_enable(ctrl_base);
+ brcmusb_usb3_enable_pipe_reset(ctrl_base);
+ brcmusb_usb3_enable_sigdet(ctrl_base);
+ brcmusb_usb3_enable_skip_align(ctrl_base);
+ brcmusb_usb3_unfreeze_aeq(ctrl_base);
+}
+
+static void brcmusb_memc_fix(struct brcm_usb_init_params *params)
+{
+ u32 prid;
+
+ if (params->selected_family != BRCM_FAMILY_7445D0)
+ return;
+ /*
+ * This is a workaround for HW7445-1869 where a DMA write ends up
+ * doing a read pre-fetch after the end of the DMA buffer. This
+ * causes a problem when the DMA buffer is at the end of physical
+ * memory, causing the pre-fetch read to access non-existent memory,
+ * and the chip bondout has MEMC2 disabled. When the pre-fetch read
+ * tries to use the disabled MEMC2, it hangs the bus. The workaround
+ * is to disable MEMC2 access in the usb controller which avoids
+ * the hang.
+ */
+
+ prid = params->product_id & 0xfffff000;
+ switch (prid) {
+ case 0x72520000:
+ case 0x74480000:
+ case 0x74490000:
+ case 0x07252000:
+ case 0x07448000:
+ case 0x07449000:
+ USB_CTRL_UNSET_FAMILY(params, SETUP, SCB2_EN);
+ }
+}
+
+static void brcmusb_usb3_otp_fix(struct brcm_usb_init_params *params)
+{
+ void __iomem *xhci_ec_base = params->xhci_ec_regs;
+ u32 val;
+
+ if (params->family_id != 0x74371000 || xhci_ec_base == 0)
+ return;
+ brcmusb_writel(0xa20c, USB_XHCI_EC_REG(xhci_ec_base, IRAADR));
+ val = brcmusb_readl(USB_XHCI_EC_REG(xhci_ec_base, IRADAT));
+
+ /* set cfg_pick_ss_lock */
+ val |= (1 << 27);
+ brcmusb_writel(val, USB_XHCI_EC_REG(xhci_ec_base, IRADAT));
+
+ /* Reset USB 3.0 PHY for workaround to take effect */
+ USB_CTRL_UNSET(params->ctrl_regs, USB30_CTL1, PHY3_RESETB);
+ USB_CTRL_SET(params->ctrl_regs, USB30_CTL1, PHY3_RESETB);
+}
+
+static void brcmusb_xhci_soft_reset(struct brcm_usb_init_params *params,
+ int on_off)
+{
+ /* Assert reset */
+ if (on_off) {
+ if (USB_CTRL_MASK_FAMILY(params, USB_PM, XHC_SOFT_RESETB))
+ USB_CTRL_UNSET_FAMILY(params, USB_PM, XHC_SOFT_RESETB);
+ else
+ USB_CTRL_UNSET_FAMILY(params,
+ USB30_CTL1, XHC_SOFT_RESETB);
+ } else { /* De-assert reset */
+ if (USB_CTRL_MASK_FAMILY(params, USB_PM, XHC_SOFT_RESETB))
+ USB_CTRL_SET_FAMILY(params, USB_PM, XHC_SOFT_RESETB);
+ else
+ USB_CTRL_SET_FAMILY(params, USB30_CTL1,
+ XHC_SOFT_RESETB);
+ }
+}
+
+/*
+ * Return the best map table family. The order is:
+ * - exact match of chip and major rev
+ * - exact match of chip and closest older major rev
+ * - default chip/rev.
+ * NOTE: The minor rev is always ignored.
+ */
+static enum brcm_family_type brcmusb_get_family_type(
+ struct brcm_usb_init_params *params)
+{
+ int last_type = -1;
+ u32 last_family = 0;
+ u32 family_no_major;
+ unsigned int x;
+ u32 family;
+
+ family = params->family_id & 0xfffffff0;
+ family_no_major = params->family_id & 0xffffff00;
+ for (x = 0; id_to_type_table[x].id; x++) {
+ if (family == id_to_type_table[x].id)
+ return id_to_type_table[x].type;
+ if (family_no_major == (id_to_type_table[x].id & 0xffffff00))
+ if (family > id_to_type_table[x].id &&
+ last_family < id_to_type_table[x].id) {
+ last_family = id_to_type_table[x].id;
+ last_type = id_to_type_table[x].type;
+ }
+ }
+
+ /* If no match, return the default family */
+ if (last_type == -1)
+ return id_to_type_table[x].type;
+ return last_type;
+}
+
+void brcm_usb_init_ipp(struct brcm_usb_init_params *params)
+{
+ void __iomem *ctrl = params->ctrl_regs;
+ u32 reg;
+ u32 orig_reg;
+
+ /* Starting with the 7445d0, there are no longer separate 3.0
+ * versions of IOC and IPP.
+ */
+ if (USB_CTRL_MASK_FAMILY(params, USB30_CTL1, USB3_IOC)) {
+ if (params->ioc)
+ USB_CTRL_SET_FAMILY(params, USB30_CTL1, USB3_IOC);
+ if (params->ipp == 1)
+ USB_CTRL_SET_FAMILY(params, USB30_CTL1, USB3_IPP);
+ }
+
+ reg = brcmusb_readl(USB_CTRL_REG(ctrl, SETUP));
+ orig_reg = reg;
+ if (USB_CTRL_MASK_FAMILY(params, SETUP, STRAP_CC_DRD_MODE_ENABLE_SEL))
+ /* Never use the strap, it's going away. */
+ reg &= ~(USB_CTRL_MASK_FAMILY(params,
+ SETUP,
+ STRAP_CC_DRD_MODE_ENABLE_SEL));
+ if (USB_CTRL_MASK_FAMILY(params, SETUP, STRAP_IPP_SEL))
+ if (params->ipp != 2)
+ /* override ipp strap pin (if it exits) */
+ reg &= ~(USB_CTRL_MASK_FAMILY(params, SETUP,
+ STRAP_IPP_SEL));
+
+ /* Override the default OC and PP polarity */
+ reg &= ~(USB_CTRL_MASK(SETUP, IPP) | USB_CTRL_MASK(SETUP, IOC));
+ if (params->ioc)
+ reg |= USB_CTRL_MASK(SETUP, IOC);
+ if (params->ipp == 1 && ((reg & USB_CTRL_MASK(SETUP, IPP)) == 0))
+ reg |= USB_CTRL_MASK(SETUP, IPP);
+ brcmusb_writel(reg, USB_CTRL_REG(ctrl, SETUP));
+
+ /*
+ * If we're changing IPP, make sure power is off long enough
+ * to turn off any connected devices.
+ */
+ if (reg != orig_reg)
+ msleep(50);
+}
+
+int brcm_usb_init_get_dual_select(struct brcm_usb_init_params *params)
+{
+ void __iomem *ctrl = params->ctrl_regs;
+ u32 reg = 0;
+
+ if (USB_CTRL_MASK_FAMILY(params, USB_DEVICE_CTL1, PORT_MODE)) {
+ reg = brcmusb_readl(USB_CTRL_REG(ctrl, USB_DEVICE_CTL1));
+ reg &= USB_CTRL_MASK_FAMILY(params, USB_DEVICE_CTL1,
+ PORT_MODE);
+ }
+ return reg;
+}
+
+void brcm_usb_init_set_dual_select(struct brcm_usb_init_params *params,
+ int mode)
+{
+ void __iomem *ctrl = params->ctrl_regs;
+ u32 reg;
+
+ if (USB_CTRL_MASK_FAMILY(params, USB_DEVICE_CTL1, PORT_MODE)) {
+ reg = brcmusb_readl(USB_CTRL_REG(ctrl, USB_DEVICE_CTL1));
+ reg &= ~USB_CTRL_MASK_FAMILY(params, USB_DEVICE_CTL1,
+ PORT_MODE);
+ reg |= mode;
+ brcmusb_writel(reg, USB_CTRL_REG(ctrl, USB_DEVICE_CTL1));
+ }
+}
+
+void brcm_usb_init_common(struct brcm_usb_init_params *params)
+{
+ u32 reg;
+ void __iomem *ctrl = params->ctrl_regs;
+
+ /* Take USB out of power down */
+ if (USB_CTRL_MASK_FAMILY(params, PLL_CTL, PLL_IDDQ_PWRDN)) {
+ USB_CTRL_UNSET_FAMILY(params, PLL_CTL, PLL_IDDQ_PWRDN);
+ /* 1 millisecond - for USB clocks to settle down */
+ usleep_range(1000, 2000);
+ }
+
+ if (USB_CTRL_MASK_FAMILY(params, USB_PM, USB_PWRDN)) {
+ USB_CTRL_UNSET_FAMILY(params, USB_PM, USB_PWRDN);
+ /* 1 millisecond - for USB clocks to settle down */
+ usleep_range(1000, 2000);
+ }
+
+ if (params->selected_family != BRCM_FAMILY_74371A0 &&
+ (BRCM_ID(params->family_id) != 0x7364))
+ /*
+ * HW7439-637: 7439a0 and its derivatives do not have large
+ * enough descriptor storage for this.
+ */
+ USB_CTRL_SET_FAMILY(params, SETUP, SS_EHCI64BIT_EN);
+
+ /* Block auto PLL suspend by USB2 PHY (Sasi) */
+ USB_CTRL_SET(ctrl, PLL_CTL, PLL_SUSPEND_EN);
+
+ reg = brcmusb_readl(USB_CTRL_REG(ctrl, SETUP));
+ if (params->selected_family == BRCM_FAMILY_7364A0)
+ /* Suppress overcurrent indication from USB30 ports for A0 */
+ reg |= USB_CTRL_MASK_FAMILY(params, SETUP, OC3_DISABLE);
+
+ brcmusb_usb_phy_ldo_fix(ctrl);
+ brcmusb_usb2_eye_fix(ctrl);
+
+ /*
+ * Make sure the the second and third memory controller
+ * interfaces are enabled if they exist.
+ */
+ if (USB_CTRL_MASK_FAMILY(params, SETUP, SCB1_EN))
+ reg |= USB_CTRL_MASK_FAMILY(params, SETUP, SCB1_EN);
+ if (USB_CTRL_MASK_FAMILY(params, SETUP, SCB2_EN))
+ reg |= USB_CTRL_MASK_FAMILY(params, SETUP, SCB2_EN);
+ brcmusb_writel(reg, USB_CTRL_REG(ctrl, SETUP));
+
+ brcmusb_memc_fix(params);
+
+ if (USB_CTRL_MASK_FAMILY(params, USB_DEVICE_CTL1, PORT_MODE)) {
+ reg = brcmusb_readl(USB_CTRL_REG(ctrl, USB_DEVICE_CTL1));
+ reg &= ~USB_CTRL_MASK_FAMILY(params, USB_DEVICE_CTL1,
+ PORT_MODE);
+ reg |= params->mode;
+ brcmusb_writel(reg, USB_CTRL_REG(ctrl, USB_DEVICE_CTL1));
+ }
+ if (USB_CTRL_MASK_FAMILY(params, USB_PM, BDC_SOFT_RESETB)) {
+ switch (params->mode) {
+ case USB_CTLR_MODE_HOST:
+ USB_CTRL_UNSET_FAMILY(params, USB_PM, BDC_SOFT_RESETB);
+ break;
+ default:
+ USB_CTRL_UNSET_FAMILY(params, USB_PM, BDC_SOFT_RESETB);
+ USB_CTRL_SET_FAMILY(params, USB_PM, BDC_SOFT_RESETB);
+ break;
+ }
+ }
+ if (USB_CTRL_MASK_FAMILY(params, SETUP, CC_DRD_MODE_ENABLE)) {
+ if (params->mode == USB_CTLR_MODE_TYPEC_PD)
+ USB_CTRL_SET_FAMILY(params, SETUP, CC_DRD_MODE_ENABLE);
+ else
+ USB_CTRL_UNSET_FAMILY(params, SETUP,
+ CC_DRD_MODE_ENABLE);
+ }
+}
+
+void brcm_usb_init_eohci(struct brcm_usb_init_params *params)
+{
+ u32 reg;
+ void __iomem *ctrl = params->ctrl_regs;
+
+ if (USB_CTRL_MASK_FAMILY(params, USB_PM, USB20_HC_RESETB))
+ USB_CTRL_SET_FAMILY(params, USB_PM, USB20_HC_RESETB);
+
+ if (params->selected_family == BRCM_FAMILY_7366C0)
+ /*
+ * Don't enable this so the memory controller doesn't read
+ * into memory holes. NOTE: This bit is low true on 7366C0.
+ */
+ USB_CTRL_SET(ctrl, EBRIDGE, ESTOP_SCB_REQ);
+
+ /* Setup the endian bits */
+ reg = brcmusb_readl(USB_CTRL_REG(ctrl, SETUP));
+ reg &= ~USB_CTRL_SETUP_ENDIAN_BITS;
+ reg |= USB_CTRL_MASK_FAMILY(params, SETUP, ENDIAN);
+ brcmusb_writel(reg, USB_CTRL_REG(ctrl, SETUP));
+
+ if (params->selected_family == BRCM_FAMILY_7271A0)
+ /* Enable LS keep alive fix for certain keyboards */
+ USB_CTRL_SET(ctrl, OBRIDGE, LS_KEEP_ALIVE);
+}
+
+void brcm_usb_init_xhci(struct brcm_usb_init_params *params)
+{
+ void __iomem *ctrl = params->ctrl_regs;
+
+ USB_CTRL_UNSET(ctrl, USB30_PCTL, PHY3_IDDQ_OVERRIDE);
+ /* 1 millisecond - for USB clocks to settle down */
+ usleep_range(1000, 2000);
+
+ if (BRCM_ID(params->family_id) == 0x7366) {
+ /*
+ * The PHY3_SOFT_RESETB bits default to the wrong state.
+ */
+ USB_CTRL_SET(ctrl, USB30_PCTL, PHY3_SOFT_RESETB);
+ USB_CTRL_SET(ctrl, USB30_PCTL, PHY3_SOFT_RESETB_P1);
+ }
+
+ /*
+ * Kick start USB3 PHY
+ * Make sure it's low to insure a rising edge.
+ */
+ USB_CTRL_UNSET(ctrl, USB30_CTL1, PHY3_PLL_SEQ_START);
+ USB_CTRL_SET(ctrl, USB30_CTL1, PHY3_PLL_SEQ_START);
+
+ brcmusb_usb3_phy_workarounds(params);
+ brcmusb_xhci_soft_reset(params, 0);
+ brcmusb_usb3_otp_fix(params);
+}
+
+void brcm_usb_uninit_common(struct brcm_usb_init_params *params)
+{
+ if (USB_CTRL_MASK_FAMILY(params, USB_PM, USB_PWRDN))
+ USB_CTRL_SET_FAMILY(params, USB_PM, USB_PWRDN);
+
+ if (USB_CTRL_MASK_FAMILY(params, PLL_CTL, PLL_IDDQ_PWRDN))
+ USB_CTRL_SET_FAMILY(params, PLL_CTL, PLL_IDDQ_PWRDN);
+}
+
+void brcm_usb_uninit_eohci(struct brcm_usb_init_params *params)
+{
+ if (USB_CTRL_MASK_FAMILY(params, USB_PM, USB20_HC_RESETB))
+ USB_CTRL_UNSET_FAMILY(params, USB_PM, USB20_HC_RESETB);
+}
+
+void brcm_usb_uninit_xhci(struct brcm_usb_init_params *params)
+{
+ brcmusb_xhci_soft_reset(params, 1);
+ USB_CTRL_SET(params->ctrl_regs, USB30_PCTL, PHY3_IDDQ_OVERRIDE);
+}
+
+void brcm_usb_set_family_map(struct brcm_usb_init_params *params)
+{
+ int fam;
+
+ fam = brcmusb_get_family_type(params);
+ params->selected_family = fam;
+ params->usb_reg_bits_map =
+ &usb_reg_bits_map_table[fam][0];
+ params->family_name = family_names[fam];
+}
diff --git a/drivers/phy/broadcom/phy-brcm-usb-init.h b/drivers/phy/broadcom/phy-brcm-usb-init.h
new file mode 100644
index 000000000..bb77b8638
--- /dev/null
+++ b/drivers/phy/broadcom/phy-brcm-usb-init.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014-2017 Broadcom
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _USB_BRCM_COMMON_INIT_H
+#define _USB_BRCM_COMMON_INIT_H
+
+#define USB_CTLR_MODE_HOST 0
+#define USB_CTLR_MODE_DEVICE 1
+#define USB_CTLR_MODE_DRD 2
+#define USB_CTLR_MODE_TYPEC_PD 3
+
+struct brcm_usb_init_params;
+
+struct brcm_usb_init_params {
+ void __iomem *ctrl_regs;
+ void __iomem *xhci_ec_regs;
+ int ioc;
+ int ipp;
+ int mode;
+ u32 family_id;
+ u32 product_id;
+ int selected_family;
+ const char *family_name;
+ const u32 *usb_reg_bits_map;
+};
+
+void brcm_usb_set_family_map(struct brcm_usb_init_params *params);
+int brcm_usb_init_get_dual_select(struct brcm_usb_init_params *params);
+void brcm_usb_init_set_dual_select(struct brcm_usb_init_params *params,
+ int mode);
+
+void brcm_usb_init_ipp(struct brcm_usb_init_params *ini);
+void brcm_usb_init_common(struct brcm_usb_init_params *ini);
+void brcm_usb_init_eohci(struct brcm_usb_init_params *ini);
+void brcm_usb_init_xhci(struct brcm_usb_init_params *ini);
+void brcm_usb_uninit_common(struct brcm_usb_init_params *ini);
+void brcm_usb_uninit_eohci(struct brcm_usb_init_params *ini);
+void brcm_usb_uninit_xhci(struct brcm_usb_init_params *ini);
+
+#endif /* _USB_BRCM_COMMON_INIT_H */
diff --git a/drivers/phy/broadcom/phy-brcm-usb.c b/drivers/phy/broadcom/phy-brcm-usb.c
new file mode 100644
index 000000000..e2455ffb8
--- /dev/null
+++ b/drivers/phy/broadcom/phy-brcm-usb.c
@@ -0,0 +1,467 @@
+/*
+ * phy-brcm-usb.c - Broadcom USB Phy Driver
+ *
+ * Copyright (C) 2015-2017 Broadcom
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/soc/brcmstb/brcmstb.h>
+#include <dt-bindings/phy/phy.h>
+
+#include "phy-brcm-usb-init.h"
+
+static DEFINE_MUTEX(sysfs_lock);
+
+enum brcm_usb_phy_id {
+ BRCM_USB_PHY_2_0 = 0,
+ BRCM_USB_PHY_3_0,
+ BRCM_USB_PHY_ID_MAX
+};
+
+struct value_to_name_map {
+ int value;
+ const char *name;
+};
+
+static struct value_to_name_map brcm_dr_mode_to_name[] = {
+ { USB_CTLR_MODE_HOST, "host" },
+ { USB_CTLR_MODE_DEVICE, "peripheral" },
+ { USB_CTLR_MODE_DRD, "drd" },
+ { USB_CTLR_MODE_TYPEC_PD, "typec-pd" }
+};
+
+static struct value_to_name_map brcm_dual_mode_to_name[] = {
+ { 0, "host" },
+ { 1, "device" },
+ { 2, "auto" },
+};
+
+struct brcm_usb_phy {
+ struct phy *phy;
+ unsigned int id;
+ bool inited;
+};
+
+struct brcm_usb_phy_data {
+ struct brcm_usb_init_params ini;
+ bool has_eohci;
+ bool has_xhci;
+ struct clk *usb_20_clk;
+ struct clk *usb_30_clk;
+ struct mutex mutex; /* serialize phy init */
+ int init_count;
+ struct brcm_usb_phy phys[BRCM_USB_PHY_ID_MAX];
+};
+
+static int brcm_usb_phy_init(struct phy *gphy)
+{
+ struct brcm_usb_phy *phy = phy_get_drvdata(gphy);
+ struct brcm_usb_phy_data *priv =
+ container_of(phy, struct brcm_usb_phy_data, phys[phy->id]);
+
+ /*
+ * Use a lock to make sure a second caller waits until
+ * the base phy is inited before using it.
+ */
+ mutex_lock(&priv->mutex);
+ if (priv->init_count++ == 0) {
+ clk_enable(priv->usb_20_clk);
+ clk_enable(priv->usb_30_clk);
+ brcm_usb_init_common(&priv->ini);
+ }
+ mutex_unlock(&priv->mutex);
+ if (phy->id == BRCM_USB_PHY_2_0)
+ brcm_usb_init_eohci(&priv->ini);
+ else if (phy->id == BRCM_USB_PHY_3_0)
+ brcm_usb_init_xhci(&priv->ini);
+ phy->inited = true;
+ dev_dbg(&gphy->dev, "INIT, id: %d, total: %d\n", phy->id,
+ priv->init_count);
+
+ return 0;
+}
+
+static int brcm_usb_phy_exit(struct phy *gphy)
+{
+ struct brcm_usb_phy *phy = phy_get_drvdata(gphy);
+ struct brcm_usb_phy_data *priv =
+ container_of(phy, struct brcm_usb_phy_data, phys[phy->id]);
+
+ dev_dbg(&gphy->dev, "EXIT\n");
+ if (phy->id == BRCM_USB_PHY_2_0)
+ brcm_usb_uninit_eohci(&priv->ini);
+ if (phy->id == BRCM_USB_PHY_3_0)
+ brcm_usb_uninit_xhci(&priv->ini);
+
+ /* If both xhci and eohci are gone, reset everything else */
+ mutex_lock(&priv->mutex);
+ if (--priv->init_count == 0) {
+ brcm_usb_uninit_common(&priv->ini);
+ clk_disable(priv->usb_20_clk);
+ clk_disable(priv->usb_30_clk);
+ }
+ mutex_unlock(&priv->mutex);
+ phy->inited = false;
+ return 0;
+}
+
+static struct phy_ops brcm_usb_phy_ops = {
+ .init = brcm_usb_phy_init,
+ .exit = brcm_usb_phy_exit,
+ .owner = THIS_MODULE,
+};
+
+static struct phy *brcm_usb_phy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct brcm_usb_phy_data *data = dev_get_drvdata(dev);
+
+ /*
+ * values 0 and 1 are for backward compatibility with
+ * device tree nodes from older bootloaders.
+ */
+ switch (args->args[0]) {
+ case 0:
+ case PHY_TYPE_USB2:
+ if (data->phys[BRCM_USB_PHY_2_0].phy)
+ return data->phys[BRCM_USB_PHY_2_0].phy;
+ dev_warn(dev, "Error, 2.0 Phy not found\n");
+ break;
+ case 1:
+ case PHY_TYPE_USB3:
+ if (data->phys[BRCM_USB_PHY_3_0].phy)
+ return data->phys[BRCM_USB_PHY_3_0].phy;
+ dev_warn(dev, "Error, 3.0 Phy not found\n");
+ break;
+ }
+ return ERR_PTR(-ENODEV);
+}
+
+static int name_to_value(struct value_to_name_map *table, int count,
+ const char *name, int *value)
+{
+ int x;
+
+ *value = 0;
+ for (x = 0; x < count; x++) {
+ if (sysfs_streq(name, table[x].name)) {
+ *value = x;
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static const char *value_to_name(struct value_to_name_map *table, int count,
+ int value)
+{
+ if (value >= count)
+ return "unknown";
+ return table[value].name;
+}
+
+static ssize_t dr_mode_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct brcm_usb_phy_data *priv = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%s\n",
+ value_to_name(&brcm_dr_mode_to_name[0],
+ ARRAY_SIZE(brcm_dr_mode_to_name),
+ priv->ini.mode));
+}
+static DEVICE_ATTR_RO(dr_mode);
+
+static ssize_t dual_select_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct brcm_usb_phy_data *priv = dev_get_drvdata(dev);
+ int value;
+ int res;
+
+ mutex_lock(&sysfs_lock);
+ res = name_to_value(&brcm_dual_mode_to_name[0],
+ ARRAY_SIZE(brcm_dual_mode_to_name), buf, &value);
+ if (!res) {
+ brcm_usb_init_set_dual_select(&priv->ini, value);
+ res = len;
+ }
+ mutex_unlock(&sysfs_lock);
+ return res;
+}
+
+static ssize_t dual_select_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct brcm_usb_phy_data *priv = dev_get_drvdata(dev);
+ int value;
+
+ mutex_lock(&sysfs_lock);
+ value = brcm_usb_init_get_dual_select(&priv->ini);
+ mutex_unlock(&sysfs_lock);
+ return sprintf(buf, "%s\n",
+ value_to_name(&brcm_dual_mode_to_name[0],
+ ARRAY_SIZE(brcm_dual_mode_to_name),
+ value));
+}
+static DEVICE_ATTR_RW(dual_select);
+
+static struct attribute *brcm_usb_phy_attrs[] = {
+ &dev_attr_dr_mode.attr,
+ &dev_attr_dual_select.attr,
+ NULL
+};
+
+static const struct attribute_group brcm_usb_phy_group = {
+ .attrs = brcm_usb_phy_attrs,
+};
+
+static int brcm_usb_phy_dvr_init(struct device *dev,
+ struct brcm_usb_phy_data *priv,
+ struct device_node *dn)
+{
+ struct phy *gphy;
+ int err;
+
+ priv->usb_20_clk = of_clk_get_by_name(dn, "sw_usb");
+ if (IS_ERR(priv->usb_20_clk)) {
+ dev_info(dev, "Clock not found in Device Tree\n");
+ priv->usb_20_clk = NULL;
+ }
+ err = clk_prepare_enable(priv->usb_20_clk);
+ if (err)
+ return err;
+
+ if (priv->has_eohci) {
+ gphy = devm_phy_create(dev, NULL, &brcm_usb_phy_ops);
+ if (IS_ERR(gphy)) {
+ dev_err(dev, "failed to create EHCI/OHCI PHY\n");
+ return PTR_ERR(gphy);
+ }
+ priv->phys[BRCM_USB_PHY_2_0].phy = gphy;
+ priv->phys[BRCM_USB_PHY_2_0].id = BRCM_USB_PHY_2_0;
+ phy_set_drvdata(gphy, &priv->phys[BRCM_USB_PHY_2_0]);
+ }
+
+ if (priv->has_xhci) {
+ gphy = devm_phy_create(dev, NULL, &brcm_usb_phy_ops);
+ if (IS_ERR(gphy)) {
+ dev_err(dev, "failed to create XHCI PHY\n");
+ return PTR_ERR(gphy);
+ }
+ priv->phys[BRCM_USB_PHY_3_0].phy = gphy;
+ priv->phys[BRCM_USB_PHY_3_0].id = BRCM_USB_PHY_3_0;
+ phy_set_drvdata(gphy, &priv->phys[BRCM_USB_PHY_3_0]);
+
+ priv->usb_30_clk = of_clk_get_by_name(dn, "sw_usb3");
+ if (IS_ERR(priv->usb_30_clk)) {
+ dev_info(dev,
+ "USB3.0 clock not found in Device Tree\n");
+ priv->usb_30_clk = NULL;
+ }
+ err = clk_prepare_enable(priv->usb_30_clk);
+ if (err)
+ return err;
+ }
+ return 0;
+}
+
+static int brcm_usb_phy_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct device *dev = &pdev->dev;
+ struct brcm_usb_phy_data *priv;
+ struct phy_provider *phy_provider;
+ struct device_node *dn = pdev->dev.of_node;
+ int err;
+ const char *mode;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, priv);
+
+ priv->ini.family_id = brcmstb_get_family_id();
+ priv->ini.product_id = brcmstb_get_product_id();
+ brcm_usb_set_family_map(&priv->ini);
+ dev_dbg(dev, "Best mapping table is for %s\n",
+ priv->ini.family_name);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "can't get USB_CTRL base address\n");
+ return -EINVAL;
+ }
+ priv->ini.ctrl_regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(priv->ini.ctrl_regs)) {
+ dev_err(dev, "can't map CTRL register space\n");
+ return -EINVAL;
+ }
+
+ /* The XHCI EC registers are optional */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (res) {
+ priv->ini.xhci_ec_regs =
+ devm_ioremap_resource(dev, res);
+ if (IS_ERR(priv->ini.xhci_ec_regs)) {
+ dev_err(dev, "can't map XHCI EC register space\n");
+ return -EINVAL;
+ }
+ }
+
+ of_property_read_u32(dn, "brcm,ipp", &priv->ini.ipp);
+ of_property_read_u32(dn, "brcm,ioc", &priv->ini.ioc);
+
+ priv->ini.mode = USB_CTLR_MODE_HOST;
+ err = of_property_read_string(dn, "dr_mode", &mode);
+ if (err == 0) {
+ name_to_value(&brcm_dr_mode_to_name[0],
+ ARRAY_SIZE(brcm_dr_mode_to_name),
+ mode, &priv->ini.mode);
+ }
+ if (of_property_read_bool(dn, "brcm,has-xhci"))
+ priv->has_xhci = true;
+ if (of_property_read_bool(dn, "brcm,has-eohci"))
+ priv->has_eohci = true;
+
+ err = brcm_usb_phy_dvr_init(dev, priv, dn);
+ if (err)
+ return err;
+
+ mutex_init(&priv->mutex);
+
+ /* make sure invert settings are correct */
+ brcm_usb_init_ipp(&priv->ini);
+
+ /*
+ * Create sysfs entries for mode.
+ * Remove "dual_select" attribute if not in dual mode
+ */
+ if (priv->ini.mode != USB_CTLR_MODE_DRD)
+ brcm_usb_phy_attrs[1] = NULL;
+ err = sysfs_create_group(&dev->kobj, &brcm_usb_phy_group);
+ if (err)
+ dev_warn(dev, "Error creating sysfs attributes\n");
+
+ /* start with everything off */
+ if (priv->has_xhci)
+ brcm_usb_uninit_xhci(&priv->ini);
+ if (priv->has_eohci)
+ brcm_usb_uninit_eohci(&priv->ini);
+ brcm_usb_uninit_common(&priv->ini);
+ clk_disable(priv->usb_20_clk);
+ clk_disable(priv->usb_30_clk);
+
+ phy_provider = devm_of_phy_provider_register(dev, brcm_usb_phy_xlate);
+ if (IS_ERR(phy_provider))
+ return PTR_ERR(phy_provider);
+
+ return 0;
+}
+
+static int brcm_usb_phy_remove(struct platform_device *pdev)
+{
+ sysfs_remove_group(&pdev->dev.kobj, &brcm_usb_phy_group);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int brcm_usb_phy_suspend(struct device *dev)
+{
+ struct brcm_usb_phy_data *priv = dev_get_drvdata(dev);
+
+ if (priv->init_count) {
+ clk_disable(priv->usb_20_clk);
+ clk_disable(priv->usb_30_clk);
+ }
+ return 0;
+}
+
+static int brcm_usb_phy_resume(struct device *dev)
+{
+ struct brcm_usb_phy_data *priv = dev_get_drvdata(dev);
+
+ clk_enable(priv->usb_20_clk);
+ clk_enable(priv->usb_30_clk);
+ brcm_usb_init_ipp(&priv->ini);
+
+ /*
+ * Initialize anything that was previously initialized.
+ * Uninitialize anything that wasn't previously initialized.
+ */
+ if (priv->init_count) {
+ brcm_usb_init_common(&priv->ini);
+ if (priv->phys[BRCM_USB_PHY_2_0].inited) {
+ brcm_usb_init_eohci(&priv->ini);
+ } else if (priv->has_eohci) {
+ brcm_usb_uninit_eohci(&priv->ini);
+ clk_disable(priv->usb_20_clk);
+ }
+ if (priv->phys[BRCM_USB_PHY_3_0].inited) {
+ brcm_usb_init_xhci(&priv->ini);
+ } else if (priv->has_xhci) {
+ brcm_usb_uninit_xhci(&priv->ini);
+ clk_disable(priv->usb_30_clk);
+ }
+ } else {
+ if (priv->has_xhci)
+ brcm_usb_uninit_xhci(&priv->ini);
+ if (priv->has_eohci)
+ brcm_usb_uninit_eohci(&priv->ini);
+ brcm_usb_uninit_common(&priv->ini);
+ clk_disable(priv->usb_20_clk);
+ clk_disable(priv->usb_30_clk);
+ }
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops brcm_usb_phy_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(brcm_usb_phy_suspend, brcm_usb_phy_resume)
+};
+
+static const struct of_device_id brcm_usb_dt_ids[] = {
+ { .compatible = "brcm,brcmstb-usb-phy" },
+ { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, brcm_usb_dt_ids);
+
+static struct platform_driver brcm_usb_driver = {
+ .probe = brcm_usb_phy_probe,
+ .remove = brcm_usb_phy_remove,
+ .driver = {
+ .name = "brcmstb-usb-phy",
+ .owner = THIS_MODULE,
+ .pm = &brcm_usb_phy_pm_ops,
+ .of_match_table = brcm_usb_dt_ids,
+ },
+};
+
+module_platform_driver(brcm_usb_driver);
+
+MODULE_ALIAS("platform:brcmstb-usb-phy");
+MODULE_AUTHOR("Al Cooper <acooper@broadcom.com>");
+MODULE_DESCRIPTION("BRCM USB PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/hisilicon/Kconfig b/drivers/phy/hisilicon/Kconfig
new file mode 100644
index 000000000..b40ee54a1
--- /dev/null
+++ b/drivers/phy/hisilicon/Kconfig
@@ -0,0 +1,40 @@
+#
+# Phy drivers for Hisilicon platforms
+#
+config PHY_HI6220_USB
+ tristate "hi6220 USB PHY support"
+ depends on (ARCH_HISI && ARM64) || COMPILE_TEST
+ depends on HAS_IOMEM
+ select GENERIC_PHY
+ select MFD_SYSCON
+ help
+ Enable this to support the HISILICON HI6220 USB PHY.
+
+ To compile this driver as a module, choose M here.
+
+config PHY_HISTB_COMBPHY
+ tristate "HiSilicon STB SoCs COMBPHY support"
+ depends on (ARCH_HISI && ARM64) || COMPILE_TEST
+ select GENERIC_PHY
+ select MFD_SYSCON
+ help
+ Enable this to support the HISILICON STB SoCs COMBPHY.
+ If unsure, say N.
+
+config PHY_HISI_INNO_USB2
+ tristate "HiSilicon INNO USB2 PHY support"
+ depends on (ARCH_HISI && ARM64) || COMPILE_TEST
+ select GENERIC_PHY
+ select MFD_SYSCON
+ help
+ Support for INNO USB2 PHY on HiSilicon SoCs. This Phy supports
+ USB 1.5Mb/s, USB 12Mb/s, USB 480Mb/s speeds. It supports one
+ USB host port to accept one USB device.
+
+config PHY_HIX5HD2_SATA
+ tristate "HIX5HD2 SATA PHY Driver"
+ depends on ARCH_HIX5HD2 && OF && HAS_IOMEM
+ select GENERIC_PHY
+ select MFD_SYSCON
+ help
+ Support for SATA PHY on Hisilicon hix5hd2 Soc.
diff --git a/drivers/phy/hisilicon/Makefile b/drivers/phy/hisilicon/Makefile
new file mode 100644
index 000000000..f662a4fe1
--- /dev/null
+++ b/drivers/phy/hisilicon/Makefile
@@ -0,0 +1,4 @@
+obj-$(CONFIG_PHY_HI6220_USB) += phy-hi6220-usb.o
+obj-$(CONFIG_PHY_HISTB_COMBPHY) += phy-histb-combphy.o
+obj-$(CONFIG_PHY_HISI_INNO_USB2) += phy-hisi-inno-usb2.o
+obj-$(CONFIG_PHY_HIX5HD2_SATA) += phy-hix5hd2-sata.o
diff --git a/drivers/phy/hisilicon/phy-hi6220-usb.c b/drivers/phy/hisilicon/phy-hi6220-usb.c
new file mode 100644
index 000000000..398c1021d
--- /dev/null
+++ b/drivers/phy/hisilicon/phy-hi6220-usb.c
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2015 Linaro Ltd.
+ * Copyright (c) 2015 Hisilicon Limited.
+ *
+ * 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.
+ */
+
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+
+#define SC_PERIPH_CTRL4 0x00c
+
+#define CTRL4_PICO_SIDDQ BIT(6)
+#define CTRL4_PICO_OGDISABLE BIT(8)
+#define CTRL4_PICO_VBUSVLDEXT BIT(10)
+#define CTRL4_PICO_VBUSVLDEXTSEL BIT(11)
+#define CTRL4_OTG_PHY_SEL BIT(21)
+
+#define SC_PERIPH_CTRL5 0x010
+
+#define CTRL5_USBOTG_RES_SEL BIT(3)
+#define CTRL5_PICOPHY_ACAENB BIT(4)
+#define CTRL5_PICOPHY_BC_MODE BIT(5)
+#define CTRL5_PICOPHY_CHRGSEL BIT(6)
+#define CTRL5_PICOPHY_VDATSRCEND BIT(7)
+#define CTRL5_PICOPHY_VDATDETENB BIT(8)
+#define CTRL5_PICOPHY_DCDENB BIT(9)
+#define CTRL5_PICOPHY_IDDIG BIT(10)
+
+#define SC_PERIPH_CTRL8 0x018
+#define SC_PERIPH_RSTEN0 0x300
+#define SC_PERIPH_RSTDIS0 0x304
+
+#define RST0_USBOTG_BUS BIT(4)
+#define RST0_POR_PICOPHY BIT(5)
+#define RST0_USBOTG BIT(6)
+#define RST0_USBOTG_32K BIT(7)
+
+#define EYE_PATTERN_PARA 0x7053348c
+
+struct hi6220_priv {
+ struct regmap *reg;
+ struct device *dev;
+};
+
+static void hi6220_phy_init(struct hi6220_priv *priv)
+{
+ struct regmap *reg = priv->reg;
+ u32 val, mask;
+
+ val = RST0_USBOTG_BUS | RST0_POR_PICOPHY |
+ RST0_USBOTG | RST0_USBOTG_32K;
+ mask = val;
+ regmap_update_bits(reg, SC_PERIPH_RSTEN0, mask, val);
+ regmap_update_bits(reg, SC_PERIPH_RSTDIS0, mask, val);
+}
+
+static int hi6220_phy_setup(struct hi6220_priv *priv, bool on)
+{
+ struct regmap *reg = priv->reg;
+ u32 val, mask;
+ int ret;
+
+ if (on) {
+ val = CTRL5_USBOTG_RES_SEL | CTRL5_PICOPHY_ACAENB;
+ mask = val | CTRL5_PICOPHY_BC_MODE;
+ ret = regmap_update_bits(reg, SC_PERIPH_CTRL5, mask, val);
+ if (ret)
+ goto out;
+
+ val = CTRL4_PICO_VBUSVLDEXT | CTRL4_PICO_VBUSVLDEXTSEL |
+ CTRL4_OTG_PHY_SEL;
+ mask = val | CTRL4_PICO_SIDDQ | CTRL4_PICO_OGDISABLE;
+ ret = regmap_update_bits(reg, SC_PERIPH_CTRL4, mask, val);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(reg, SC_PERIPH_CTRL8, EYE_PATTERN_PARA);
+ if (ret)
+ goto out;
+ } else {
+ val = CTRL4_PICO_SIDDQ;
+ mask = val;
+ ret = regmap_update_bits(reg, SC_PERIPH_CTRL4, mask, val);
+ if (ret)
+ goto out;
+ }
+
+ return 0;
+out:
+ dev_err(priv->dev, "failed to setup phy ret: %d\n", ret);
+ return ret;
+}
+
+static int hi6220_phy_start(struct phy *phy)
+{
+ struct hi6220_priv *priv = phy_get_drvdata(phy);
+
+ return hi6220_phy_setup(priv, true);
+}
+
+static int hi6220_phy_exit(struct phy *phy)
+{
+ struct hi6220_priv *priv = phy_get_drvdata(phy);
+
+ return hi6220_phy_setup(priv, false);
+}
+
+static const struct phy_ops hi6220_phy_ops = {
+ .init = hi6220_phy_start,
+ .exit = hi6220_phy_exit,
+ .owner = THIS_MODULE,
+};
+
+static int hi6220_phy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct phy *phy;
+ struct hi6220_priv *priv;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->reg = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "hisilicon,peripheral-syscon");
+ if (IS_ERR(priv->reg)) {
+ dev_err(dev, "no hisilicon,peripheral-syscon\n");
+ return PTR_ERR(priv->reg);
+ }
+
+ hi6220_phy_init(priv);
+
+ phy = devm_phy_create(dev, NULL, &hi6220_phy_ops);
+ if (IS_ERR(phy))
+ return PTR_ERR(phy);
+
+ phy_set_drvdata(phy, priv);
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id hi6220_phy_of_match[] = {
+ {.compatible = "hisilicon,hi6220-usb-phy",},
+ { },
+};
+MODULE_DEVICE_TABLE(of, hi6220_phy_of_match);
+
+static struct platform_driver hi6220_phy_driver = {
+ .probe = hi6220_phy_probe,
+ .driver = {
+ .name = "hi6220-usb-phy",
+ .of_match_table = hi6220_phy_of_match,
+ }
+};
+module_platform_driver(hi6220_phy_driver);
+
+MODULE_DESCRIPTION("HISILICON HI6220 USB PHY driver");
+MODULE_ALIAS("platform:hi6220-usb-phy");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/hisilicon/phy-hisi-inno-usb2.c b/drivers/phy/hisilicon/phy-hisi-inno-usb2.c
new file mode 100644
index 000000000..524381249
--- /dev/null
+++ b/drivers/phy/hisilicon/phy-hisi-inno-usb2.c
@@ -0,0 +1,197 @@
+/*
+ * HiSilicon INNO USB2 PHY Driver.
+ *
+ * Copyright (c) 2016-2017 HiSilicon Technologies 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+#include <linux/reset.h>
+
+#define INNO_PHY_PORT_NUM 2
+#define REF_CLK_STABLE_TIME 100 /* unit:us */
+#define UTMI_CLK_STABLE_TIME 200 /* unit:us */
+#define TEST_CLK_STABLE_TIME 2 /* unit:ms */
+#define PHY_CLK_STABLE_TIME 2 /* unit:ms */
+#define UTMI_RST_COMPLETE_TIME 2 /* unit:ms */
+#define POR_RST_COMPLETE_TIME 300 /* unit:us */
+#define PHY_TEST_DATA GENMASK(7, 0)
+#define PHY_TEST_ADDR GENMASK(15, 8)
+#define PHY_TEST_PORT GENMASK(18, 16)
+#define PHY_TEST_WREN BIT(21)
+#define PHY_TEST_CLK BIT(22) /* rising edge active */
+#define PHY_TEST_RST BIT(23) /* low active */
+#define PHY_CLK_ENABLE BIT(2)
+
+struct hisi_inno_phy_port {
+ struct reset_control *utmi_rst;
+ struct hisi_inno_phy_priv *priv;
+};
+
+struct hisi_inno_phy_priv {
+ void __iomem *mmio;
+ struct clk *ref_clk;
+ struct reset_control *por_rst;
+ struct hisi_inno_phy_port ports[INNO_PHY_PORT_NUM];
+};
+
+static void hisi_inno_phy_write_reg(struct hisi_inno_phy_priv *priv,
+ u8 port, u32 addr, u32 data)
+{
+ void __iomem *reg = priv->mmio;
+ u32 val;
+
+ val = (data & PHY_TEST_DATA) |
+ ((addr << 8) & PHY_TEST_ADDR) |
+ ((port << 16) & PHY_TEST_PORT) |
+ PHY_TEST_WREN | PHY_TEST_RST;
+ writel(val, reg);
+
+ val |= PHY_TEST_CLK;
+ writel(val, reg);
+
+ val &= ~PHY_TEST_CLK;
+ writel(val, reg);
+}
+
+static void hisi_inno_phy_setup(struct hisi_inno_phy_priv *priv)
+{
+ /* The phy clk is controlled by the port0 register 0x06. */
+ hisi_inno_phy_write_reg(priv, 0, 0x06, PHY_CLK_ENABLE);
+ msleep(PHY_CLK_STABLE_TIME);
+}
+
+static int hisi_inno_phy_init(struct phy *phy)
+{
+ struct hisi_inno_phy_port *port = phy_get_drvdata(phy);
+ struct hisi_inno_phy_priv *priv = port->priv;
+ int ret;
+
+ ret = clk_prepare_enable(priv->ref_clk);
+ if (ret)
+ return ret;
+ udelay(REF_CLK_STABLE_TIME);
+
+ reset_control_deassert(priv->por_rst);
+ udelay(POR_RST_COMPLETE_TIME);
+
+ /* Set up phy registers */
+ hisi_inno_phy_setup(priv);
+
+ reset_control_deassert(port->utmi_rst);
+ udelay(UTMI_RST_COMPLETE_TIME);
+
+ return 0;
+}
+
+static int hisi_inno_phy_exit(struct phy *phy)
+{
+ struct hisi_inno_phy_port *port = phy_get_drvdata(phy);
+ struct hisi_inno_phy_priv *priv = port->priv;
+
+ reset_control_assert(port->utmi_rst);
+ reset_control_assert(priv->por_rst);
+ clk_disable_unprepare(priv->ref_clk);
+
+ return 0;
+}
+
+static const struct phy_ops hisi_inno_phy_ops = {
+ .init = hisi_inno_phy_init,
+ .exit = hisi_inno_phy_exit,
+ .owner = THIS_MODULE,
+};
+
+static int hisi_inno_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct hisi_inno_phy_priv *priv;
+ struct phy_provider *provider;
+ struct device_node *child;
+ struct resource *res;
+ int i = 0;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->mmio = devm_ioremap_resource(dev, res);
+ if (IS_ERR(priv->mmio)) {
+ ret = PTR_ERR(priv->mmio);
+ return ret;
+ }
+
+ priv->ref_clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->ref_clk))
+ return PTR_ERR(priv->ref_clk);
+
+ priv->por_rst = devm_reset_control_get_exclusive(dev, NULL);
+ if (IS_ERR(priv->por_rst))
+ return PTR_ERR(priv->por_rst);
+
+ for_each_child_of_node(np, child) {
+ struct reset_control *rst;
+ struct phy *phy;
+
+ rst = of_reset_control_get_exclusive(child, NULL);
+ if (IS_ERR(rst))
+ return PTR_ERR(rst);
+ priv->ports[i].utmi_rst = rst;
+ priv->ports[i].priv = priv;
+
+ phy = devm_phy_create(dev, child, &hisi_inno_phy_ops);
+ if (IS_ERR(phy))
+ return PTR_ERR(phy);
+
+ phy_set_bus_width(phy, 8);
+ phy_set_drvdata(phy, &priv->ports[i]);
+ i++;
+
+ if (i > INNO_PHY_PORT_NUM) {
+ dev_warn(dev, "Support %d ports in maximum\n", i);
+ break;
+ }
+ }
+
+ provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ return PTR_ERR_OR_ZERO(provider);
+}
+
+static const struct of_device_id hisi_inno_phy_of_match[] = {
+ { .compatible = "hisilicon,inno-usb2-phy", },
+ { .compatible = "hisilicon,hi3798cv200-usb2-phy", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, hisi_inno_phy_of_match);
+
+static struct platform_driver hisi_inno_phy_driver = {
+ .probe = hisi_inno_phy_probe,
+ .driver = {
+ .name = "hisi-inno-phy",
+ .of_match_table = hisi_inno_phy_of_match,
+ }
+};
+module_platform_driver(hisi_inno_phy_driver);
+
+MODULE_DESCRIPTION("HiSilicon INNO USB2 PHY Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/hisilicon/phy-histb-combphy.c b/drivers/phy/hisilicon/phy-histb-combphy.c
new file mode 100644
index 000000000..5777b3120
--- /dev/null
+++ b/drivers/phy/hisilicon/phy-histb-combphy.c
@@ -0,0 +1,289 @@
+/*
+ * COMBPHY driver for HiSilicon STB SoCs
+ *
+ * Copyright (C) 2016-2017 HiSilicon Co., Ltd. http://www.hisilicon.com
+ *
+ * Authors: Jianguo Sun <sunjianguo1@huawei.com>
+ *
+ * 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/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <dt-bindings/phy/phy.h>
+
+#define COMBPHY_MODE_PCIE 0
+#define COMBPHY_MODE_USB3 1
+#define COMBPHY_MODE_SATA 2
+
+#define COMBPHY_CFG_REG 0x0
+#define COMBPHY_BYPASS_CODEC BIT(31)
+#define COMBPHY_TEST_WRITE BIT(24)
+#define COMBPHY_TEST_DATA_SHIFT 20
+#define COMBPHY_TEST_DATA_MASK GENMASK(23, 20)
+#define COMBPHY_TEST_ADDR_SHIFT 12
+#define COMBPHY_TEST_ADDR_MASK GENMASK(16, 12)
+#define COMBPHY_CLKREF_OUT_OEN BIT(0)
+
+struct histb_combphy_mode {
+ int fixed;
+ int select;
+ u32 reg;
+ u32 shift;
+ u32 mask;
+};
+
+struct histb_combphy_priv {
+ void __iomem *mmio;
+ struct regmap *syscon;
+ struct reset_control *por_rst;
+ struct clk *ref_clk;
+ struct phy *phy;
+ struct histb_combphy_mode mode;
+};
+
+static void nano_register_write(struct histb_combphy_priv *priv,
+ u32 addr, u32 data)
+{
+ void __iomem *reg = priv->mmio + COMBPHY_CFG_REG;
+ u32 val;
+
+ /* Set up address and data for the write */
+ val = readl(reg);
+ val &= ~COMBPHY_TEST_ADDR_MASK;
+ val |= addr << COMBPHY_TEST_ADDR_SHIFT;
+ val &= ~COMBPHY_TEST_DATA_MASK;
+ val |= data << COMBPHY_TEST_DATA_SHIFT;
+ writel(val, reg);
+
+ /* Flip strobe control to trigger the write */
+ val &= ~COMBPHY_TEST_WRITE;
+ writel(val, reg);
+ val |= COMBPHY_TEST_WRITE;
+ writel(val, reg);
+}
+
+static int is_mode_fixed(struct histb_combphy_mode *mode)
+{
+ return (mode->fixed != PHY_NONE) ? true : false;
+}
+
+static int histb_combphy_set_mode(struct histb_combphy_priv *priv)
+{
+ struct histb_combphy_mode *mode = &priv->mode;
+ struct regmap *syscon = priv->syscon;
+ u32 hw_sel;
+
+ if (is_mode_fixed(mode))
+ return 0;
+
+ switch (mode->select) {
+ case PHY_TYPE_SATA:
+ hw_sel = COMBPHY_MODE_SATA;
+ break;
+ case PHY_TYPE_PCIE:
+ hw_sel = COMBPHY_MODE_PCIE;
+ break;
+ case PHY_TYPE_USB3:
+ hw_sel = COMBPHY_MODE_USB3;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return regmap_update_bits(syscon, mode->reg, mode->mask,
+ hw_sel << mode->shift);
+}
+
+static int histb_combphy_init(struct phy *phy)
+{
+ struct histb_combphy_priv *priv = phy_get_drvdata(phy);
+ u32 val;
+ int ret;
+
+ ret = histb_combphy_set_mode(priv);
+ if (ret)
+ return ret;
+
+ /* Clear bypass bit to enable encoding/decoding */
+ val = readl(priv->mmio + COMBPHY_CFG_REG);
+ val &= ~COMBPHY_BYPASS_CODEC;
+ writel(val, priv->mmio + COMBPHY_CFG_REG);
+
+ ret = clk_prepare_enable(priv->ref_clk);
+ if (ret)
+ return ret;
+
+ reset_control_deassert(priv->por_rst);
+
+ /* Enable EP clock */
+ val = readl(priv->mmio + COMBPHY_CFG_REG);
+ val |= COMBPHY_CLKREF_OUT_OEN;
+ writel(val, priv->mmio + COMBPHY_CFG_REG);
+
+ /* Need to wait for EP clock stable */
+ mdelay(5);
+
+ /* Configure nano phy registers as suggested by vendor */
+ nano_register_write(priv, 0x1, 0x8);
+ nano_register_write(priv, 0xc, 0x9);
+ nano_register_write(priv, 0x1a, 0x4);
+
+ return 0;
+}
+
+static int histb_combphy_exit(struct phy *phy)
+{
+ struct histb_combphy_priv *priv = phy_get_drvdata(phy);
+ u32 val;
+
+ /* Disable EP clock */
+ val = readl(priv->mmio + COMBPHY_CFG_REG);
+ val &= ~COMBPHY_CLKREF_OUT_OEN;
+ writel(val, priv->mmio + COMBPHY_CFG_REG);
+
+ reset_control_assert(priv->por_rst);
+ clk_disable_unprepare(priv->ref_clk);
+
+ return 0;
+}
+
+static const struct phy_ops histb_combphy_ops = {
+ .init = histb_combphy_init,
+ .exit = histb_combphy_exit,
+ .owner = THIS_MODULE,
+};
+
+static struct phy *histb_combphy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct histb_combphy_priv *priv = dev_get_drvdata(dev);
+ struct histb_combphy_mode *mode = &priv->mode;
+
+ if (args->args_count < 1) {
+ dev_err(dev, "invalid number of arguments\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ mode->select = args->args[0];
+
+ if (mode->select < PHY_TYPE_SATA || mode->select > PHY_TYPE_USB3) {
+ dev_err(dev, "invalid phy mode select argument\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (is_mode_fixed(mode) && mode->select != mode->fixed) {
+ dev_err(dev, "mode select %d mismatch fixed phy mode %d\n",
+ mode->select, mode->fixed);
+ return ERR_PTR(-EINVAL);
+ }
+
+ return priv->phy;
+}
+
+static int histb_combphy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct histb_combphy_priv *priv;
+ struct device_node *np = dev->of_node;
+ struct histb_combphy_mode *mode;
+ struct resource *res;
+ u32 vals[3];
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->mmio = devm_ioremap_resource(dev, res);
+ if (IS_ERR(priv->mmio)) {
+ ret = PTR_ERR(priv->mmio);
+ return ret;
+ }
+
+ priv->syscon = syscon_node_to_regmap(np->parent);
+ if (IS_ERR(priv->syscon)) {
+ dev_err(dev, "failed to find peri_ctrl syscon regmap\n");
+ return PTR_ERR(priv->syscon);
+ }
+
+ mode = &priv->mode;
+ mode->fixed = PHY_NONE;
+
+ ret = of_property_read_u32(np, "hisilicon,fixed-mode", &mode->fixed);
+ if (ret == 0)
+ dev_dbg(dev, "found fixed phy mode %d\n", mode->fixed);
+
+ ret = of_property_read_u32_array(np, "hisilicon,mode-select-bits",
+ vals, ARRAY_SIZE(vals));
+ if (ret == 0) {
+ if (is_mode_fixed(mode)) {
+ dev_err(dev, "found select bits for fixed mode phy\n");
+ return -EINVAL;
+ }
+
+ mode->reg = vals[0];
+ mode->shift = vals[1];
+ mode->mask = vals[2];
+ dev_dbg(dev, "found mode select bits\n");
+ } else {
+ if (!is_mode_fixed(mode)) {
+ dev_err(dev, "no valid select bits found for non-fixed phy\n");
+ return -ENODEV;
+ }
+ }
+
+ priv->ref_clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->ref_clk)) {
+ dev_err(dev, "failed to find ref clock\n");
+ return PTR_ERR(priv->ref_clk);
+ }
+
+ priv->por_rst = devm_reset_control_get(dev, NULL);
+ if (IS_ERR(priv->por_rst)) {
+ dev_err(dev, "failed to get poweron reset\n");
+ return PTR_ERR(priv->por_rst);
+ }
+
+ priv->phy = devm_phy_create(dev, NULL, &histb_combphy_ops);
+ if (IS_ERR(priv->phy)) {
+ dev_err(dev, "failed to create combphy\n");
+ return PTR_ERR(priv->phy);
+ }
+
+ dev_set_drvdata(dev, priv);
+ phy_set_drvdata(priv->phy, priv);
+
+ phy_provider = devm_of_phy_provider_register(dev, histb_combphy_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id histb_combphy_of_match[] = {
+ { .compatible = "hisilicon,hi3798cv200-combphy" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, histb_combphy_of_match);
+
+static struct platform_driver histb_combphy_driver = {
+ .probe = histb_combphy_probe,
+ .driver = {
+ .name = "combphy",
+ .of_match_table = histb_combphy_of_match,
+ },
+};
+module_platform_driver(histb_combphy_driver);
+
+MODULE_DESCRIPTION("HiSilicon STB COMBPHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/hisilicon/phy-hix5hd2-sata.c b/drivers/phy/hisilicon/phy-hix5hd2-sata.c
new file mode 100644
index 000000000..e5ab3aa78
--- /dev/null
+++ b/drivers/phy/hisilicon/phy-hix5hd2-sata.c
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2014 Linaro Ltd.
+ * Copyright (c) 2014 Hisilicon Limited.
+ *
+ * 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.
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define SATA_PHY0_CTLL 0xa0
+#define MPLL_MULTIPLIER_SHIFT 1
+#define MPLL_MULTIPLIER_MASK 0xfe
+#define MPLL_MULTIPLIER_50M 0x3c
+#define MPLL_MULTIPLIER_100M 0x1e
+#define PHY_RESET BIT(0)
+#define REF_SSP_EN BIT(9)
+#define SSC_EN BIT(10)
+#define REF_USE_PAD BIT(23)
+
+#define SATA_PORT_PHYCTL 0x174
+#define SPEED_MODE_MASK 0x6f0000
+#define HALF_RATE_SHIFT 16
+#define PHY_CONFIG_SHIFT 18
+#define GEN2_EN_SHIFT 21
+#define SPEED_CTRL BIT(20)
+
+#define SATA_PORT_PHYCTL1 0x148
+#define AMPLITUDE_MASK 0x3ffffe
+#define AMPLITUDE_GEN3 0x68
+#define AMPLITUDE_GEN3_SHIFT 15
+#define AMPLITUDE_GEN2 0x56
+#define AMPLITUDE_GEN2_SHIFT 8
+#define AMPLITUDE_GEN1 0x56
+#define AMPLITUDE_GEN1_SHIFT 1
+
+#define SATA_PORT_PHYCTL2 0x14c
+#define PREEMPH_MASK 0x3ffff
+#define PREEMPH_GEN3 0x20
+#define PREEMPH_GEN3_SHIFT 12
+#define PREEMPH_GEN2 0x15
+#define PREEMPH_GEN2_SHIFT 6
+#define PREEMPH_GEN1 0x5
+#define PREEMPH_GEN1_SHIFT 0
+
+struct hix5hd2_priv {
+ void __iomem *base;
+ struct regmap *peri_ctrl;
+};
+
+enum phy_speed_mode {
+ SPEED_MODE_GEN1 = 0,
+ SPEED_MODE_GEN2 = 1,
+ SPEED_MODE_GEN3 = 2,
+};
+
+static int hix5hd2_sata_phy_init(struct phy *phy)
+{
+ struct hix5hd2_priv *priv = phy_get_drvdata(phy);
+ u32 val, data[2];
+ int ret;
+
+ if (priv->peri_ctrl) {
+ ret = of_property_read_u32_array(phy->dev.of_node,
+ "hisilicon,power-reg",
+ &data[0], 2);
+ if (ret) {
+ dev_err(&phy->dev, "Fail read hisilicon,power-reg\n");
+ return ret;
+ }
+
+ regmap_update_bits(priv->peri_ctrl, data[0],
+ BIT(data[1]), BIT(data[1]));
+ }
+
+ /* reset phy */
+ val = readl_relaxed(priv->base + SATA_PHY0_CTLL);
+ val &= ~(MPLL_MULTIPLIER_MASK | REF_USE_PAD);
+ val |= MPLL_MULTIPLIER_50M << MPLL_MULTIPLIER_SHIFT |
+ REF_SSP_EN | PHY_RESET;
+ writel_relaxed(val, priv->base + SATA_PHY0_CTLL);
+ msleep(20);
+ val &= ~PHY_RESET;
+ writel_relaxed(val, priv->base + SATA_PHY0_CTLL);
+
+ val = readl_relaxed(priv->base + SATA_PORT_PHYCTL1);
+ val &= ~AMPLITUDE_MASK;
+ val |= AMPLITUDE_GEN3 << AMPLITUDE_GEN3_SHIFT |
+ AMPLITUDE_GEN2 << AMPLITUDE_GEN2_SHIFT |
+ AMPLITUDE_GEN1 << AMPLITUDE_GEN1_SHIFT;
+ writel_relaxed(val, priv->base + SATA_PORT_PHYCTL1);
+
+ val = readl_relaxed(priv->base + SATA_PORT_PHYCTL2);
+ val &= ~PREEMPH_MASK;
+ val |= PREEMPH_GEN3 << PREEMPH_GEN3_SHIFT |
+ PREEMPH_GEN2 << PREEMPH_GEN2_SHIFT |
+ PREEMPH_GEN1 << PREEMPH_GEN1_SHIFT;
+ writel_relaxed(val, priv->base + SATA_PORT_PHYCTL2);
+
+ /* ensure PHYCTRL setting takes effect */
+ val = readl_relaxed(priv->base + SATA_PORT_PHYCTL);
+ val &= ~SPEED_MODE_MASK;
+ val |= SPEED_MODE_GEN1 << HALF_RATE_SHIFT |
+ SPEED_MODE_GEN1 << PHY_CONFIG_SHIFT |
+ SPEED_MODE_GEN1 << GEN2_EN_SHIFT | SPEED_CTRL;
+ writel_relaxed(val, priv->base + SATA_PORT_PHYCTL);
+
+ msleep(20);
+ val &= ~SPEED_MODE_MASK;
+ val |= SPEED_MODE_GEN3 << HALF_RATE_SHIFT |
+ SPEED_MODE_GEN3 << PHY_CONFIG_SHIFT |
+ SPEED_MODE_GEN3 << GEN2_EN_SHIFT | SPEED_CTRL;
+ writel_relaxed(val, priv->base + SATA_PORT_PHYCTL);
+
+ val &= ~(SPEED_MODE_MASK | SPEED_CTRL);
+ val |= SPEED_MODE_GEN2 << HALF_RATE_SHIFT |
+ SPEED_MODE_GEN2 << PHY_CONFIG_SHIFT |
+ SPEED_MODE_GEN2 << GEN2_EN_SHIFT;
+ writel_relaxed(val, priv->base + SATA_PORT_PHYCTL);
+
+ return 0;
+}
+
+static const struct phy_ops hix5hd2_sata_phy_ops = {
+ .init = hix5hd2_sata_phy_init,
+ .owner = THIS_MODULE,
+};
+
+static int hix5hd2_sata_phy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct phy *phy;
+ struct hix5hd2_priv *priv;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -EINVAL;
+
+ priv->base = devm_ioremap(dev, res->start, resource_size(res));
+ if (!priv->base)
+ return -ENOMEM;
+
+ priv->peri_ctrl = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "hisilicon,peripheral-syscon");
+ if (IS_ERR(priv->peri_ctrl))
+ priv->peri_ctrl = NULL;
+
+ phy = devm_phy_create(dev, NULL, &hix5hd2_sata_phy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create PHY\n");
+ return PTR_ERR(phy);
+ }
+
+ phy_set_drvdata(phy, priv);
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id hix5hd2_sata_phy_of_match[] = {
+ {.compatible = "hisilicon,hix5hd2-sata-phy",},
+ { },
+};
+MODULE_DEVICE_TABLE(of, hix5hd2_sata_phy_of_match);
+
+static struct platform_driver hix5hd2_sata_phy_driver = {
+ .probe = hix5hd2_sata_phy_probe,
+ .driver = {
+ .name = "hix5hd2-sata-phy",
+ .of_match_table = hix5hd2_sata_phy_of_match,
+ }
+};
+module_platform_driver(hix5hd2_sata_phy_driver);
+
+MODULE_AUTHOR("Jiancheng Xue <xuejiancheng@huawei.com>");
+MODULE_DESCRIPTION("HISILICON HIX5HD2 SATA PHY driver");
+MODULE_ALIAS("platform:hix5hd2-sata-phy");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/lantiq/Kconfig b/drivers/phy/lantiq/Kconfig
new file mode 100644
index 000000000..326d88a64
--- /dev/null
+++ b/drivers/phy/lantiq/Kconfig
@@ -0,0 +1,9 @@
+#
+# Phy drivers for Lantiq / Intel platforms
+#
+config PHY_LANTIQ_RCU_USB2
+ tristate "Lantiq XWAY SoC RCU based USB PHY"
+ depends on OF && (SOC_TYPE_XWAY || COMPILE_TEST)
+ select GENERIC_PHY
+ help
+ Support for the USB PHY(s) on the Lantiq / Intel XWAY family SoCs.
diff --git a/drivers/phy/lantiq/Makefile b/drivers/phy/lantiq/Makefile
new file mode 100644
index 000000000..f73eb56a5
--- /dev/null
+++ b/drivers/phy/lantiq/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_PHY_LANTIQ_RCU_USB2) += phy-lantiq-rcu-usb2.o
diff --git a/drivers/phy/lantiq/phy-lantiq-rcu-usb2.c b/drivers/phy/lantiq/phy-lantiq-rcu-usb2.c
new file mode 100644
index 000000000..5a180f71d
--- /dev/null
+++ b/drivers/phy/lantiq/phy-lantiq-rcu-usb2.c
@@ -0,0 +1,253 @@
+/*
+ * Lantiq XWAY SoC RCU module based USB 1.1/2.0 PHY driver
+ *
+ * Copyright (C) 2016 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ * Copyright (C) 2017 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * 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/clk.h>
+#include <linux/delay.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+/* Transmitter HS Pre-Emphasis Enable */
+#define RCU_CFG1_TX_PEE BIT(0)
+/* Disconnect Threshold */
+#define RCU_CFG1_DIS_THR_MASK 0x00038000
+#define RCU_CFG1_DIS_THR_SHIFT 15
+
+struct ltq_rcu_usb2_bits {
+ u8 hostmode;
+ u8 slave_endianness;
+ u8 host_endianness;
+ bool have_ana_cfg;
+};
+
+struct ltq_rcu_usb2_priv {
+ struct regmap *regmap;
+ unsigned int phy_reg_offset;
+ unsigned int ana_cfg1_reg_offset;
+ const struct ltq_rcu_usb2_bits *reg_bits;
+ struct device *dev;
+ struct phy *phy;
+ struct clk *phy_gate_clk;
+ struct reset_control *ctrl_reset;
+ struct reset_control *phy_reset;
+};
+
+static const struct ltq_rcu_usb2_bits xway_rcu_usb2_reg_bits = {
+ .hostmode = 11,
+ .slave_endianness = 9,
+ .host_endianness = 10,
+ .have_ana_cfg = false,
+};
+
+static const struct ltq_rcu_usb2_bits xrx100_rcu_usb2_reg_bits = {
+ .hostmode = 11,
+ .slave_endianness = 17,
+ .host_endianness = 10,
+ .have_ana_cfg = false,
+};
+
+static const struct ltq_rcu_usb2_bits xrx200_rcu_usb2_reg_bits = {
+ .hostmode = 11,
+ .slave_endianness = 9,
+ .host_endianness = 10,
+ .have_ana_cfg = true,
+};
+
+static const struct of_device_id ltq_rcu_usb2_phy_of_match[] = {
+ {
+ .compatible = "lantiq,ase-usb2-phy",
+ .data = &xway_rcu_usb2_reg_bits,
+ },
+ {
+ .compatible = "lantiq,danube-usb2-phy",
+ .data = &xway_rcu_usb2_reg_bits,
+ },
+ {
+ .compatible = "lantiq,xrx100-usb2-phy",
+ .data = &xrx100_rcu_usb2_reg_bits,
+ },
+ {
+ .compatible = "lantiq,xrx200-usb2-phy",
+ .data = &xrx200_rcu_usb2_reg_bits,
+ },
+ {
+ .compatible = "lantiq,xrx300-usb2-phy",
+ .data = &xrx200_rcu_usb2_reg_bits,
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ltq_rcu_usb2_phy_of_match);
+
+static int ltq_rcu_usb2_phy_init(struct phy *phy)
+{
+ struct ltq_rcu_usb2_priv *priv = phy_get_drvdata(phy);
+
+ if (priv->reg_bits->have_ana_cfg) {
+ regmap_update_bits(priv->regmap, priv->ana_cfg1_reg_offset,
+ RCU_CFG1_TX_PEE, RCU_CFG1_TX_PEE);
+ regmap_update_bits(priv->regmap, priv->ana_cfg1_reg_offset,
+ RCU_CFG1_DIS_THR_MASK, 7 << RCU_CFG1_DIS_THR_SHIFT);
+ }
+
+ /* Configure core to host mode */
+ regmap_update_bits(priv->regmap, priv->phy_reg_offset,
+ BIT(priv->reg_bits->hostmode), 0);
+
+ /* Select DMA endianness (Host-endian: big-endian) */
+ regmap_update_bits(priv->regmap, priv->phy_reg_offset,
+ BIT(priv->reg_bits->slave_endianness), 0);
+ regmap_update_bits(priv->regmap, priv->phy_reg_offset,
+ BIT(priv->reg_bits->host_endianness),
+ BIT(priv->reg_bits->host_endianness));
+
+ return 0;
+}
+
+static int ltq_rcu_usb2_phy_power_on(struct phy *phy)
+{
+ struct ltq_rcu_usb2_priv *priv = phy_get_drvdata(phy);
+ struct device *dev = priv->dev;
+ int ret;
+
+ reset_control_deassert(priv->phy_reset);
+
+ ret = clk_prepare_enable(priv->phy_gate_clk);
+ if (ret)
+ dev_err(dev, "failed to enable PHY gate\n");
+
+ return ret;
+}
+
+static int ltq_rcu_usb2_phy_power_off(struct phy *phy)
+{
+ struct ltq_rcu_usb2_priv *priv = phy_get_drvdata(phy);
+
+ reset_control_assert(priv->phy_reset);
+
+ clk_disable_unprepare(priv->phy_gate_clk);
+
+ return 0;
+}
+
+static struct phy_ops ltq_rcu_usb2_phy_ops = {
+ .init = ltq_rcu_usb2_phy_init,
+ .power_on = ltq_rcu_usb2_phy_power_on,
+ .power_off = ltq_rcu_usb2_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int ltq_rcu_usb2_of_parse(struct ltq_rcu_usb2_priv *priv,
+ struct platform_device *pdev)
+{
+ struct device *dev = priv->dev;
+ const __be32 *offset;
+
+ priv->reg_bits = of_device_get_match_data(dev);
+
+ priv->regmap = syscon_node_to_regmap(dev->of_node->parent);
+ if (IS_ERR(priv->regmap)) {
+ dev_err(dev, "Failed to lookup RCU regmap\n");
+ return PTR_ERR(priv->regmap);
+ }
+
+ offset = of_get_address(dev->of_node, 0, NULL, NULL);
+ if (!offset) {
+ dev_err(dev, "Failed to get RCU PHY reg offset\n");
+ return -ENOENT;
+ }
+ priv->phy_reg_offset = __be32_to_cpu(*offset);
+
+ if (priv->reg_bits->have_ana_cfg) {
+ offset = of_get_address(dev->of_node, 1, NULL, NULL);
+ if (!offset) {
+ dev_err(dev, "Failed to get RCU ANA CFG1 reg offset\n");
+ return -ENOENT;
+ }
+ priv->ana_cfg1_reg_offset = __be32_to_cpu(*offset);
+ }
+
+ priv->phy_gate_clk = devm_clk_get(dev, "phy");
+ if (IS_ERR(priv->phy_gate_clk)) {
+ dev_err(dev, "Unable to get USB phy gate clk\n");
+ return PTR_ERR(priv->phy_gate_clk);
+ }
+
+ priv->ctrl_reset = devm_reset_control_get_shared(dev, "ctrl");
+ if (IS_ERR(priv->ctrl_reset)) {
+ if (PTR_ERR(priv->ctrl_reset) != -EPROBE_DEFER)
+ dev_err(dev, "failed to get 'ctrl' reset\n");
+ return PTR_ERR(priv->ctrl_reset);
+ }
+
+ priv->phy_reset = devm_reset_control_get_optional(dev, "phy");
+ if (IS_ERR(priv->phy_reset))
+ return PTR_ERR(priv->phy_reset);
+
+ return 0;
+}
+
+static int ltq_rcu_usb2_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ltq_rcu_usb2_priv *priv;
+ struct phy_provider *provider;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+
+ ret = ltq_rcu_usb2_of_parse(priv, pdev);
+ if (ret)
+ return ret;
+
+ /* Reset USB core through reset controller */
+ reset_control_deassert(priv->ctrl_reset);
+
+ reset_control_assert(priv->phy_reset);
+
+ priv->phy = devm_phy_create(dev, dev->of_node, &ltq_rcu_usb2_phy_ops);
+ if (IS_ERR(priv->phy)) {
+ dev_err(dev, "failed to create PHY\n");
+ return PTR_ERR(priv->phy);
+ }
+
+ phy_set_drvdata(priv->phy, priv);
+
+ provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(provider))
+ return PTR_ERR(provider);
+
+ dev_set_drvdata(priv->dev, priv);
+ return 0;
+}
+
+static struct platform_driver ltq_rcu_usb2_phy_driver = {
+ .probe = ltq_rcu_usb2_phy_probe,
+ .driver = {
+ .name = "lantiq-rcu-usb2-phy",
+ .of_match_table = ltq_rcu_usb2_phy_of_match,
+ }
+};
+module_platform_driver(ltq_rcu_usb2_phy_driver);
+
+MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
+MODULE_DESCRIPTION("Lantiq XWAY USB2 PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/marvell/Kconfig b/drivers/phy/marvell/Kconfig
new file mode 100644
index 000000000..ed4d3904e
--- /dev/null
+++ b/drivers/phy/marvell/Kconfig
@@ -0,0 +1,61 @@
+#
+# Phy drivers for Marvell platforms
+#
+config ARMADA375_USBCLUSTER_PHY
+ bool "Armada 375 USB cluster PHY support" if COMPILE_TEST
+ default y if MACH_ARMADA_375
+ depends on OF && HAS_IOMEM
+ select GENERIC_PHY
+
+config PHY_BERLIN_SATA
+ tristate "Marvell Berlin SATA PHY driver"
+ depends on ARCH_BERLIN && HAS_IOMEM && OF
+ select GENERIC_PHY
+ help
+ Enable this to support the SATA PHY on Marvell Berlin SoCs.
+
+config PHY_BERLIN_USB
+ tristate "Marvell Berlin USB PHY Driver"
+ depends on ARCH_BERLIN && RESET_CONTROLLER && HAS_IOMEM && OF
+ select GENERIC_PHY
+ help
+ Enable this to support the USB PHY on Marvell Berlin SoCs.
+
+config PHY_MVEBU_CP110_COMPHY
+ tristate "Marvell CP110 comphy driver"
+ depends on ARCH_MVEBU || COMPILE_TEST
+ depends on OF
+ select GENERIC_PHY
+ help
+ This driver allows to control the comphy, an hardware block providing
+ shared serdes PHYs on Marvell Armada 7k/8k (in the CP110). Its serdes
+ lanes can be used by various controllers (Ethernet, sata, usb,
+ PCIe...).
+
+config PHY_MVEBU_SATA
+ def_bool y
+ depends on ARCH_DOVE || MACH_DOVE || MACH_KIRKWOOD
+ depends on OF
+ select GENERIC_PHY
+
+config PHY_PXA_28NM_HSIC
+ tristate "Marvell USB HSIC 28nm PHY Driver"
+ depends on HAS_IOMEM
+ select GENERIC_PHY
+ help
+ Enable this to support Marvell USB HSIC PHY driver for Marvell
+ SoC. This driver will do the PHY initialization and shutdown.
+ The PHY driver will be used by Marvell ehci driver.
+
+ To compile this driver as a module, choose M here.
+
+config PHY_PXA_28NM_USB2
+ tristate "Marvell USB 2.0 28nm PHY Driver"
+ depends on HAS_IOMEM
+ select GENERIC_PHY
+ help
+ Enable this to support Marvell USB 2.0 PHY driver for Marvell
+ SoC. This driver will do the PHY initialization and shutdown.
+ The PHY driver will be used by Marvell udc/ehci/otg driver.
+
+ To compile this driver as a module, choose M here.
diff --git a/drivers/phy/marvell/Makefile b/drivers/phy/marvell/Makefile
new file mode 100644
index 000000000..5c3ec5d10
--- /dev/null
+++ b/drivers/phy/marvell/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_ARMADA375_USBCLUSTER_PHY) += phy-armada375-usb2.o
+obj-$(CONFIG_PHY_BERLIN_SATA) += phy-berlin-sata.o
+obj-$(CONFIG_PHY_BERLIN_USB) += phy-berlin-usb.o
+obj-$(CONFIG_PHY_MVEBU_CP110_COMPHY) += phy-mvebu-cp110-comphy.o
+obj-$(CONFIG_PHY_MVEBU_SATA) += phy-mvebu-sata.o
+obj-$(CONFIG_PHY_PXA_28NM_HSIC) += phy-pxa-28nm-hsic.o
+obj-$(CONFIG_PHY_PXA_28NM_USB2) += phy-pxa-28nm-usb2.o
diff --git a/drivers/phy/marvell/phy-armada375-usb2.c b/drivers/phy/marvell/phy-armada375-usb2.c
new file mode 100644
index 000000000..1a3db288c
--- /dev/null
+++ b/drivers/phy/marvell/phy-armada375-usb2.c
@@ -0,0 +1,158 @@
+/*
+ * USB cluster support for Armada 375 platform.
+ *
+ * Copyright (C) 2014 Marvell
+ *
+ * Gregory CLEMENT <gregory.clement@free-electrons.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2 or later. This program is licensed "as is"
+ * without any warranty of any kind, whether express or implied.
+ *
+ * Armada 375 comes with an USB2 host and device controller and an
+ * USB3 controller. The USB cluster control register allows to manage
+ * common features of both USB controllers.
+ */
+
+#include <dt-bindings/phy/phy.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+#define USB2_PHY_CONFIG_DISABLE BIT(0)
+
+struct armada375_cluster_phy {
+ struct phy *phy;
+ void __iomem *reg;
+ bool use_usb3;
+ int phy_provided;
+};
+
+static int armada375_usb_phy_init(struct phy *phy)
+{
+ struct armada375_cluster_phy *cluster_phy;
+ u32 reg;
+
+ cluster_phy = phy_get_drvdata(phy);
+ if (!cluster_phy)
+ return -ENODEV;
+
+ reg = readl(cluster_phy->reg);
+ if (cluster_phy->use_usb3)
+ reg |= USB2_PHY_CONFIG_DISABLE;
+ else
+ reg &= ~USB2_PHY_CONFIG_DISABLE;
+ writel(reg, cluster_phy->reg);
+
+ return 0;
+}
+
+static const struct phy_ops armada375_usb_phy_ops = {
+ .init = armada375_usb_phy_init,
+ .owner = THIS_MODULE,
+};
+
+/*
+ * Only one controller can use this PHY. We shouldn't have the case
+ * when two controllers want to use this PHY. But if this case occurs
+ * then we provide a phy to the first one and return an error for the
+ * next one. This error has also to be an error returned by
+ * devm_phy_optional_get() so different from ENODEV for USB2. In the
+ * USB3 case it still optional and we use ENODEV.
+ */
+static struct phy *armada375_usb_phy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct armada375_cluster_phy *cluster_phy = dev_get_drvdata(dev);
+
+ if (!cluster_phy)
+ return ERR_PTR(-ENODEV);
+
+ /*
+ * Either the phy had never been requested and then the first
+ * usb claiming it can get it, or it had already been
+ * requested in this case, we only allow to use it with the
+ * same configuration.
+ */
+ if (WARN_ON((cluster_phy->phy_provided != PHY_NONE) &&
+ (cluster_phy->phy_provided != args->args[0]))) {
+ dev_err(dev, "This PHY has already been provided!\n");
+ dev_err(dev, "Check your device tree, only one controller can use it\n.");
+ if (args->args[0] == PHY_TYPE_USB2)
+ return ERR_PTR(-EBUSY);
+ else
+ return ERR_PTR(-ENODEV);
+ }
+
+ if (args->args[0] == PHY_TYPE_USB2)
+ cluster_phy->use_usb3 = false;
+ else if (args->args[0] == PHY_TYPE_USB3)
+ cluster_phy->use_usb3 = true;
+ else {
+ dev_err(dev, "Invalid PHY mode\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ /* Store which phy mode is used for next test */
+ cluster_phy->phy_provided = args->args[0];
+
+ return cluster_phy->phy;
+}
+
+static int armada375_usb_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct phy *phy;
+ struct phy_provider *phy_provider;
+ void __iomem *usb_cluster_base;
+ struct resource *res;
+ struct armada375_cluster_phy *cluster_phy;
+
+ cluster_phy = devm_kzalloc(dev, sizeof(*cluster_phy), GFP_KERNEL);
+ if (!cluster_phy)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ usb_cluster_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(usb_cluster_base))
+ return PTR_ERR(usb_cluster_base);
+
+ phy = devm_phy_create(dev, NULL, &armada375_usb_phy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create PHY\n");
+ return PTR_ERR(phy);
+ }
+
+ cluster_phy->phy = phy;
+ cluster_phy->reg = usb_cluster_base;
+
+ dev_set_drvdata(dev, cluster_phy);
+ phy_set_drvdata(phy, cluster_phy);
+
+ phy_provider = devm_of_phy_provider_register(&pdev->dev,
+ armada375_usb_phy_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id of_usb_cluster_table[] = {
+ { .compatible = "marvell,armada-375-usb-cluster", },
+ { /* end of list */ },
+};
+MODULE_DEVICE_TABLE(of, of_usb_cluster_table);
+
+static struct platform_driver armada375_usb_phy_driver = {
+ .probe = armada375_usb_phy_probe,
+ .driver = {
+ .of_match_table = of_usb_cluster_table,
+ .name = "armada-375-usb-cluster",
+ }
+};
+module_platform_driver(armada375_usb_phy_driver);
+
+MODULE_DESCRIPTION("Armada 375 USB cluster driver");
+MODULE_AUTHOR("Gregory CLEMENT <gregory.clement@free-electrons.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/marvell/phy-berlin-sata.c b/drivers/phy/marvell/phy-berlin-sata.c
new file mode 100644
index 000000000..c1bb6725e
--- /dev/null
+++ b/drivers/phy/marvell/phy-berlin-sata.c
@@ -0,0 +1,296 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Marvell Berlin SATA PHY driver
+ *
+ * Copyright (C) 2014 Marvell Technology Group Ltd.
+ *
+ * Antoine Ténart <antoine.tenart@free-electrons.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+
+#define HOST_VSA_ADDR 0x0
+#define HOST_VSA_DATA 0x4
+#define PORT_SCR_CTL 0x2c
+#define PORT_VSR_ADDR 0x78
+#define PORT_VSR_DATA 0x7c
+
+#define CONTROL_REGISTER 0x0
+#define MBUS_SIZE_CONTROL 0x4
+
+#define POWER_DOWN_PHY0 BIT(6)
+#define POWER_DOWN_PHY1 BIT(14)
+#define MBUS_WRITE_REQUEST_SIZE_128 (BIT(2) << 16)
+#define MBUS_READ_REQUEST_SIZE_128 (BIT(2) << 19)
+
+#define BG2_PHY_BASE 0x080
+#define BG2Q_PHY_BASE 0x200
+
+/* register 0x01 */
+#define REF_FREF_SEL_25 BIT(0)
+#define PHY_MODE_SATA (0x0 << 5)
+
+/* register 0x02 */
+#define USE_MAX_PLL_RATE BIT(12)
+
+/* register 0x23 */
+#define DATA_BIT_WIDTH_10 (0x0 << 10)
+#define DATA_BIT_WIDTH_20 (0x1 << 10)
+#define DATA_BIT_WIDTH_40 (0x2 << 10)
+
+/* register 0x25 */
+#define PHY_GEN_MAX_1_5 (0x0 << 10)
+#define PHY_GEN_MAX_3_0 (0x1 << 10)
+#define PHY_GEN_MAX_6_0 (0x2 << 10)
+
+struct phy_berlin_desc {
+ struct phy *phy;
+ u32 power_bit;
+ unsigned index;
+};
+
+struct phy_berlin_priv {
+ void __iomem *base;
+ spinlock_t lock;
+ struct clk *clk;
+ struct phy_berlin_desc **phys;
+ unsigned nphys;
+ u32 phy_base;
+};
+
+static inline void phy_berlin_sata_reg_setbits(void __iomem *ctrl_reg,
+ u32 phy_base, u32 reg, u32 mask, u32 val)
+{
+ u32 regval;
+
+ /* select register */
+ writel(phy_base + reg, ctrl_reg + PORT_VSR_ADDR);
+
+ /* set bits */
+ regval = readl(ctrl_reg + PORT_VSR_DATA);
+ regval &= ~mask;
+ regval |= val;
+ writel(regval, ctrl_reg + PORT_VSR_DATA);
+}
+
+static int phy_berlin_sata_power_on(struct phy *phy)
+{
+ struct phy_berlin_desc *desc = phy_get_drvdata(phy);
+ struct phy_berlin_priv *priv = dev_get_drvdata(phy->dev.parent);
+ void __iomem *ctrl_reg = priv->base + 0x60 + (desc->index * 0x80);
+ u32 regval;
+
+ clk_prepare_enable(priv->clk);
+
+ spin_lock(&priv->lock);
+
+ /* Power on PHY */
+ writel(CONTROL_REGISTER, priv->base + HOST_VSA_ADDR);
+ regval = readl(priv->base + HOST_VSA_DATA);
+ regval &= ~desc->power_bit;
+ writel(regval, priv->base + HOST_VSA_DATA);
+
+ /* Configure MBus */
+ writel(MBUS_SIZE_CONTROL, priv->base + HOST_VSA_ADDR);
+ regval = readl(priv->base + HOST_VSA_DATA);
+ regval |= MBUS_WRITE_REQUEST_SIZE_128 | MBUS_READ_REQUEST_SIZE_128;
+ writel(regval, priv->base + HOST_VSA_DATA);
+
+ /* set PHY mode and ref freq to 25 MHz */
+ phy_berlin_sata_reg_setbits(ctrl_reg, priv->phy_base, 0x01,
+ 0x00ff, REF_FREF_SEL_25 | PHY_MODE_SATA);
+
+ /* set PHY up to 6 Gbps */
+ phy_berlin_sata_reg_setbits(ctrl_reg, priv->phy_base, 0x25,
+ 0x0c00, PHY_GEN_MAX_6_0);
+
+ /* set 40 bits width */
+ phy_berlin_sata_reg_setbits(ctrl_reg, priv->phy_base, 0x23,
+ 0x0c00, DATA_BIT_WIDTH_40);
+
+ /* use max pll rate */
+ phy_berlin_sata_reg_setbits(ctrl_reg, priv->phy_base, 0x02,
+ 0x0000, USE_MAX_PLL_RATE);
+
+ /* set Gen3 controller speed */
+ regval = readl(ctrl_reg + PORT_SCR_CTL);
+ regval &= ~GENMASK(7, 4);
+ regval |= 0x30;
+ writel(regval, ctrl_reg + PORT_SCR_CTL);
+
+ spin_unlock(&priv->lock);
+
+ clk_disable_unprepare(priv->clk);
+
+ return 0;
+}
+
+static int phy_berlin_sata_power_off(struct phy *phy)
+{
+ struct phy_berlin_desc *desc = phy_get_drvdata(phy);
+ struct phy_berlin_priv *priv = dev_get_drvdata(phy->dev.parent);
+ u32 regval;
+
+ clk_prepare_enable(priv->clk);
+
+ spin_lock(&priv->lock);
+
+ /* Power down PHY */
+ writel(CONTROL_REGISTER, priv->base + HOST_VSA_ADDR);
+ regval = readl(priv->base + HOST_VSA_DATA);
+ regval |= desc->power_bit;
+ writel(regval, priv->base + HOST_VSA_DATA);
+
+ spin_unlock(&priv->lock);
+
+ clk_disable_unprepare(priv->clk);
+
+ return 0;
+}
+
+static struct phy *phy_berlin_sata_phy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct phy_berlin_priv *priv = dev_get_drvdata(dev);
+ int i;
+
+ if (WARN_ON(args->args[0] >= priv->nphys))
+ return ERR_PTR(-ENODEV);
+
+ for (i = 0; i < priv->nphys; i++) {
+ if (priv->phys[i]->index == args->args[0])
+ break;
+ }
+
+ if (i == priv->nphys)
+ return ERR_PTR(-ENODEV);
+
+ return priv->phys[i]->phy;
+}
+
+static const struct phy_ops phy_berlin_sata_ops = {
+ .power_on = phy_berlin_sata_power_on,
+ .power_off = phy_berlin_sata_power_off,
+ .owner = THIS_MODULE,
+};
+
+static u32 phy_berlin_power_down_bits[] = {
+ POWER_DOWN_PHY0,
+ POWER_DOWN_PHY1,
+};
+
+static int phy_berlin_sata_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *child;
+ struct phy *phy;
+ struct phy_provider *phy_provider;
+ struct phy_berlin_priv *priv;
+ struct resource *res;
+ int ret, i = 0;
+ u32 phy_id;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -EINVAL;
+
+ priv->base = devm_ioremap(dev, res->start, resource_size(res));
+ if (!priv->base)
+ return -ENOMEM;
+
+ priv->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->clk))
+ return PTR_ERR(priv->clk);
+
+ priv->nphys = of_get_child_count(dev->of_node);
+ if (priv->nphys == 0)
+ return -ENODEV;
+
+ priv->phys = devm_kcalloc(dev, priv->nphys, sizeof(*priv->phys),
+ GFP_KERNEL);
+ if (!priv->phys)
+ return -ENOMEM;
+
+ if (of_device_is_compatible(dev->of_node, "marvell,berlin2-sata-phy"))
+ priv->phy_base = BG2_PHY_BASE;
+ else
+ priv->phy_base = BG2Q_PHY_BASE;
+
+ dev_set_drvdata(dev, priv);
+ spin_lock_init(&priv->lock);
+
+ for_each_available_child_of_node(dev->of_node, child) {
+ struct phy_berlin_desc *phy_desc;
+
+ if (of_property_read_u32(child, "reg", &phy_id)) {
+ dev_err(dev, "missing reg property in node %s\n",
+ child->name);
+ ret = -EINVAL;
+ goto put_child;
+ }
+
+ if (phy_id >= ARRAY_SIZE(phy_berlin_power_down_bits)) {
+ dev_err(dev, "invalid reg in node %s\n", child->name);
+ ret = -EINVAL;
+ goto put_child;
+ }
+
+ phy_desc = devm_kzalloc(dev, sizeof(*phy_desc), GFP_KERNEL);
+ if (!phy_desc) {
+ ret = -ENOMEM;
+ goto put_child;
+ }
+
+ phy = devm_phy_create(dev, NULL, &phy_berlin_sata_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create PHY %d\n", phy_id);
+ ret = PTR_ERR(phy);
+ goto put_child;
+ }
+
+ phy_desc->phy = phy;
+ phy_desc->power_bit = phy_berlin_power_down_bits[phy_id];
+ phy_desc->index = phy_id;
+ phy_set_drvdata(phy, phy_desc);
+
+ priv->phys[i++] = phy_desc;
+
+ /* Make sure the PHY is off */
+ phy_berlin_sata_power_off(phy);
+ }
+
+ phy_provider =
+ devm_of_phy_provider_register(dev, phy_berlin_sata_phy_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+put_child:
+ of_node_put(child);
+ return ret;
+}
+
+static const struct of_device_id phy_berlin_sata_of_match[] = {
+ { .compatible = "marvell,berlin2-sata-phy" },
+ { .compatible = "marvell,berlin2q-sata-phy" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, phy_berlin_sata_of_match);
+
+static struct platform_driver phy_berlin_sata_driver = {
+ .probe = phy_berlin_sata_probe,
+ .driver = {
+ .name = "phy-berlin-sata",
+ .of_match_table = phy_berlin_sata_of_match,
+ },
+};
+module_platform_driver(phy_berlin_sata_driver);
+
+MODULE_DESCRIPTION("Marvell Berlin SATA PHY driver");
+MODULE_AUTHOR("Antoine Ténart <antoine.tenart@free-electrons.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/marvell/phy-berlin-usb.c b/drivers/phy/marvell/phy-berlin-usb.c
new file mode 100644
index 000000000..a43df6300
--- /dev/null
+++ b/drivers/phy/marvell/phy-berlin-usb.c
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2014 Marvell Technology Group Ltd.
+ *
+ * Antoine Tenart <antoine.tenart@free-electrons.com>
+ * Jisheng Zhang <jszhang@marvell.com>
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#define USB_PHY_PLL 0x04
+#define USB_PHY_PLL_CONTROL 0x08
+#define USB_PHY_TX_CTRL0 0x10
+#define USB_PHY_TX_CTRL1 0x14
+#define USB_PHY_TX_CTRL2 0x18
+#define USB_PHY_RX_CTRL 0x20
+#define USB_PHY_ANALOG 0x34
+
+/* USB_PHY_PLL */
+#define CLK_REF_DIV(x) ((x) << 4)
+#define FEEDBACK_CLK_DIV(x) ((x) << 8)
+
+/* USB_PHY_PLL_CONTROL */
+#define CLK_STABLE BIT(0)
+#define PLL_CTRL_PIN BIT(1)
+#define PLL_CTRL_REG BIT(2)
+#define PLL_ON BIT(3)
+#define PHASE_OFF_TOL_125 (0x0 << 5)
+#define PHASE_OFF_TOL_250 BIT(5)
+#define KVC0_CALIB (0x0 << 9)
+#define KVC0_REG_CTRL BIT(9)
+#define KVC0_HIGH (0x0 << 10)
+#define KVC0_LOW (0x3 << 10)
+#define CLK_BLK_EN BIT(13)
+
+/* USB_PHY_TX_CTRL0 */
+#define EXT_HS_RCAL_EN BIT(3)
+#define EXT_FS_RCAL_EN BIT(4)
+#define IMPCAL_VTH_DIV(x) ((x) << 5)
+#define EXT_RS_RCAL_DIV(x) ((x) << 8)
+#define EXT_FS_RCAL_DIV(x) ((x) << 12)
+
+/* USB_PHY_TX_CTRL1 */
+#define TX_VDD15_14 (0x0 << 4)
+#define TX_VDD15_15 BIT(4)
+#define TX_VDD15_16 (0x2 << 4)
+#define TX_VDD15_17 (0x3 << 4)
+#define TX_VDD12_VDD (0x0 << 6)
+#define TX_VDD12_11 BIT(6)
+#define TX_VDD12_12 (0x2 << 6)
+#define TX_VDD12_13 (0x3 << 6)
+#define LOW_VDD_EN BIT(8)
+#define TX_OUT_AMP(x) ((x) << 9)
+
+/* USB_PHY_TX_CTRL2 */
+#define TX_CHAN_CTRL_REG(x) ((x) << 0)
+#define DRV_SLEWRATE(x) ((x) << 4)
+#define IMP_CAL_FS_HS_DLY_0 (0x0 << 6)
+#define IMP_CAL_FS_HS_DLY_1 BIT(6)
+#define IMP_CAL_FS_HS_DLY_2 (0x2 << 6)
+#define IMP_CAL_FS_HS_DLY_3 (0x3 << 6)
+#define FS_DRV_EN_MASK(x) ((x) << 8)
+#define HS_DRV_EN_MASK(x) ((x) << 12)
+
+/* USB_PHY_RX_CTRL */
+#define PHASE_FREEZE_DLY_2_CL (0x0 << 0)
+#define PHASE_FREEZE_DLY_4_CL BIT(0)
+#define ACK_LENGTH_8_CL (0x0 << 2)
+#define ACK_LENGTH_12_CL BIT(2)
+#define ACK_LENGTH_16_CL (0x2 << 2)
+#define ACK_LENGTH_20_CL (0x3 << 2)
+#define SQ_LENGTH_3 (0x0 << 4)
+#define SQ_LENGTH_6 BIT(4)
+#define SQ_LENGTH_9 (0x2 << 4)
+#define SQ_LENGTH_12 (0x3 << 4)
+#define DISCON_THRESHOLD_260 (0x0 << 6)
+#define DISCON_THRESHOLD_270 BIT(6)
+#define DISCON_THRESHOLD_280 (0x2 << 6)
+#define DISCON_THRESHOLD_290 (0x3 << 6)
+#define SQ_THRESHOLD(x) ((x) << 8)
+#define LPF_COEF(x) ((x) << 12)
+#define INTPL_CUR_10 (0x0 << 14)
+#define INTPL_CUR_20 BIT(14)
+#define INTPL_CUR_30 (0x2 << 14)
+#define INTPL_CUR_40 (0x3 << 14)
+
+/* USB_PHY_ANALOG */
+#define ANA_PWR_UP BIT(1)
+#define ANA_PWR_DOWN BIT(2)
+#define V2I_VCO_RATIO(x) ((x) << 7)
+#define R_ROTATE_90 (0x0 << 10)
+#define R_ROTATE_0 BIT(10)
+#define MODE_TEST_EN BIT(11)
+#define ANA_TEST_DC_CTRL(x) ((x) << 12)
+
+static const u32 phy_berlin_pll_dividers[] = {
+ /* Berlin 2 */
+ CLK_REF_DIV(0x6) | FEEDBACK_CLK_DIV(0x55),
+ /* Berlin 2CD/Q */
+ CLK_REF_DIV(0xc) | FEEDBACK_CLK_DIV(0x54),
+};
+
+struct phy_berlin_usb_priv {
+ void __iomem *base;
+ struct reset_control *rst_ctrl;
+ u32 pll_divider;
+};
+
+static int phy_berlin_usb_power_on(struct phy *phy)
+{
+ struct phy_berlin_usb_priv *priv = phy_get_drvdata(phy);
+
+ reset_control_reset(priv->rst_ctrl);
+
+ writel(priv->pll_divider,
+ priv->base + USB_PHY_PLL);
+ writel(CLK_STABLE | PLL_CTRL_REG | PHASE_OFF_TOL_250 | KVC0_REG_CTRL |
+ CLK_BLK_EN, priv->base + USB_PHY_PLL_CONTROL);
+ writel(V2I_VCO_RATIO(0x5) | R_ROTATE_0 | ANA_TEST_DC_CTRL(0x5),
+ priv->base + USB_PHY_ANALOG);
+ writel(PHASE_FREEZE_DLY_4_CL | ACK_LENGTH_16_CL | SQ_LENGTH_12 |
+ DISCON_THRESHOLD_270 | SQ_THRESHOLD(0xa) | LPF_COEF(0x2) |
+ INTPL_CUR_30, priv->base + USB_PHY_RX_CTRL);
+
+ writel(TX_VDD12_13 | TX_OUT_AMP(0x3), priv->base + USB_PHY_TX_CTRL1);
+ writel(EXT_HS_RCAL_EN | IMPCAL_VTH_DIV(0x3) | EXT_RS_RCAL_DIV(0x4),
+ priv->base + USB_PHY_TX_CTRL0);
+
+ writel(EXT_HS_RCAL_EN | IMPCAL_VTH_DIV(0x3) | EXT_RS_RCAL_DIV(0x4) |
+ EXT_FS_RCAL_DIV(0x2), priv->base + USB_PHY_TX_CTRL0);
+
+ writel(EXT_HS_RCAL_EN | IMPCAL_VTH_DIV(0x3) | EXT_RS_RCAL_DIV(0x4),
+ priv->base + USB_PHY_TX_CTRL0);
+ writel(TX_CHAN_CTRL_REG(0xf) | DRV_SLEWRATE(0x3) | IMP_CAL_FS_HS_DLY_3 |
+ FS_DRV_EN_MASK(0xd), priv->base + USB_PHY_TX_CTRL2);
+
+ return 0;
+}
+
+static const struct phy_ops phy_berlin_usb_ops = {
+ .power_on = phy_berlin_usb_power_on,
+ .owner = THIS_MODULE,
+};
+
+static const struct of_device_id phy_berlin_usb_of_match[] = {
+ {
+ .compatible = "marvell,berlin2-usb-phy",
+ .data = &phy_berlin_pll_dividers[0],
+ },
+ {
+ .compatible = "marvell,berlin2cd-usb-phy",
+ .data = &phy_berlin_pll_dividers[1],
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, phy_berlin_usb_of_match);
+
+static int phy_berlin_usb_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *match =
+ of_match_device(phy_berlin_usb_of_match, &pdev->dev);
+ struct phy_berlin_usb_priv *priv;
+ struct resource *res;
+ struct phy *phy;
+ struct phy_provider *phy_provider;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ priv->rst_ctrl = devm_reset_control_get(&pdev->dev, NULL);
+ if (IS_ERR(priv->rst_ctrl))
+ return PTR_ERR(priv->rst_ctrl);
+
+ priv->pll_divider = *((u32 *)match->data);
+
+ phy = devm_phy_create(&pdev->dev, NULL, &phy_berlin_usb_ops);
+ if (IS_ERR(phy)) {
+ dev_err(&pdev->dev, "failed to create PHY\n");
+ return PTR_ERR(phy);
+ }
+
+ phy_set_drvdata(phy, priv);
+
+ phy_provider =
+ devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static struct platform_driver phy_berlin_usb_driver = {
+ .probe = phy_berlin_usb_probe,
+ .driver = {
+ .name = "phy-berlin-usb",
+ .of_match_table = phy_berlin_usb_of_match,
+ },
+};
+module_platform_driver(phy_berlin_usb_driver);
+
+MODULE_AUTHOR("Antoine Tenart <antoine.tenart@free-electrons.com>");
+MODULE_DESCRIPTION("Marvell Berlin PHY driver for USB");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/marvell/phy-mvebu-cp110-comphy.c b/drivers/phy/marvell/phy-mvebu-cp110-comphy.c
new file mode 100644
index 000000000..86a5f7b94
--- /dev/null
+++ b/drivers/phy/marvell/phy-mvebu-cp110-comphy.c
@@ -0,0 +1,661 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017 Marvell
+ *
+ * Antoine Tenart <antoine.tenart@free-electrons.com>
+ */
+
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/* Relative to priv->base */
+#define MVEBU_COMPHY_SERDES_CFG0(n) (0x0 + (n) * 0x1000)
+#define MVEBU_COMPHY_SERDES_CFG0_PU_PLL BIT(1)
+#define MVEBU_COMPHY_SERDES_CFG0_GEN_RX(n) ((n) << 3)
+#define MVEBU_COMPHY_SERDES_CFG0_GEN_TX(n) ((n) << 7)
+#define MVEBU_COMPHY_SERDES_CFG0_PU_RX BIT(11)
+#define MVEBU_COMPHY_SERDES_CFG0_PU_TX BIT(12)
+#define MVEBU_COMPHY_SERDES_CFG0_HALF_BUS BIT(14)
+#define MVEBU_COMPHY_SERDES_CFG1(n) (0x4 + (n) * 0x1000)
+#define MVEBU_COMPHY_SERDES_CFG1_RESET BIT(3)
+#define MVEBU_COMPHY_SERDES_CFG1_RX_INIT BIT(4)
+#define MVEBU_COMPHY_SERDES_CFG1_CORE_RESET BIT(5)
+#define MVEBU_COMPHY_SERDES_CFG1_RF_RESET BIT(6)
+#define MVEBU_COMPHY_SERDES_CFG2(n) (0x8 + (n) * 0x1000)
+#define MVEBU_COMPHY_SERDES_CFG2_DFE_EN BIT(4)
+#define MVEBU_COMPHY_SERDES_STATUS0(n) (0x18 + (n) * 0x1000)
+#define MVEBU_COMPHY_SERDES_STATUS0_TX_PLL_RDY BIT(2)
+#define MVEBU_COMPHY_SERDES_STATUS0_RX_PLL_RDY BIT(3)
+#define MVEBU_COMPHY_SERDES_STATUS0_RX_INIT BIT(4)
+#define MVEBU_COMPHY_PWRPLL_CTRL(n) (0x804 + (n) * 0x1000)
+#define MVEBU_COMPHY_PWRPLL_CTRL_RFREQ(n) ((n) << 0)
+#define MVEBU_COMPHY_PWRPLL_PHY_MODE(n) ((n) << 5)
+#define MVEBU_COMPHY_IMP_CAL(n) (0x80c + (n) * 0x1000)
+#define MVEBU_COMPHY_IMP_CAL_TX_EXT(n) ((n) << 10)
+#define MVEBU_COMPHY_IMP_CAL_TX_EXT_EN BIT(15)
+#define MVEBU_COMPHY_DFE_RES(n) (0x81c + (n) * 0x1000)
+#define MVEBU_COMPHY_DFE_RES_FORCE_GEN_TBL BIT(15)
+#define MVEBU_COMPHY_COEF(n) (0x828 + (n) * 0x1000)
+#define MVEBU_COMPHY_COEF_DFE_EN BIT(14)
+#define MVEBU_COMPHY_COEF_DFE_CTRL BIT(15)
+#define MVEBU_COMPHY_GEN1_S0(n) (0x834 + (n) * 0x1000)
+#define MVEBU_COMPHY_GEN1_S0_TX_AMP(n) ((n) << 1)
+#define MVEBU_COMPHY_GEN1_S0_TX_EMPH(n) ((n) << 7)
+#define MVEBU_COMPHY_GEN1_S1(n) (0x838 + (n) * 0x1000)
+#define MVEBU_COMPHY_GEN1_S1_RX_MUL_PI(n) ((n) << 0)
+#define MVEBU_COMPHY_GEN1_S1_RX_MUL_PF(n) ((n) << 3)
+#define MVEBU_COMPHY_GEN1_S1_RX_MUL_FI(n) ((n) << 6)
+#define MVEBU_COMPHY_GEN1_S1_RX_MUL_FF(n) ((n) << 8)
+#define MVEBU_COMPHY_GEN1_S1_RX_DFE_EN BIT(10)
+#define MVEBU_COMPHY_GEN1_S1_RX_DIV(n) ((n) << 11)
+#define MVEBU_COMPHY_GEN1_S2(n) (0x8f4 + (n) * 0x1000)
+#define MVEBU_COMPHY_GEN1_S2_TX_EMPH(n) ((n) << 0)
+#define MVEBU_COMPHY_GEN1_S2_TX_EMPH_EN BIT(4)
+#define MVEBU_COMPHY_LOOPBACK(n) (0x88c + (n) * 0x1000)
+#define MVEBU_COMPHY_LOOPBACK_DBUS_WIDTH(n) ((n) << 1)
+#define MVEBU_COMPHY_VDD_CAL0(n) (0x908 + (n) * 0x1000)
+#define MVEBU_COMPHY_VDD_CAL0_CONT_MODE BIT(15)
+#define MVEBU_COMPHY_EXT_SELV(n) (0x914 + (n) * 0x1000)
+#define MVEBU_COMPHY_EXT_SELV_RX_SAMPL(n) ((n) << 5)
+#define MVEBU_COMPHY_MISC_CTRL0(n) (0x93c + (n) * 0x1000)
+#define MVEBU_COMPHY_MISC_CTRL0_ICP_FORCE BIT(5)
+#define MVEBU_COMPHY_MISC_CTRL0_REFCLK_SEL BIT(10)
+#define MVEBU_COMPHY_RX_CTRL1(n) (0x940 + (n) * 0x1000)
+#define MVEBU_COMPHY_RX_CTRL1_RXCLK2X_SEL BIT(11)
+#define MVEBU_COMPHY_RX_CTRL1_CLK8T_EN BIT(12)
+#define MVEBU_COMPHY_SPEED_DIV(n) (0x954 + (n) * 0x1000)
+#define MVEBU_COMPHY_SPEED_DIV_TX_FORCE BIT(7)
+#define MVEBU_SP_CALIB(n) (0x96c + (n) * 0x1000)
+#define MVEBU_SP_CALIB_SAMPLER(n) ((n) << 8)
+#define MVEBU_SP_CALIB_SAMPLER_EN BIT(12)
+#define MVEBU_COMPHY_TX_SLEW_RATE(n) (0x974 + (n) * 0x1000)
+#define MVEBU_COMPHY_TX_SLEW_RATE_EMPH(n) ((n) << 5)
+#define MVEBU_COMPHY_TX_SLEW_RATE_SLC(n) ((n) << 10)
+#define MVEBU_COMPHY_DLT_CTRL(n) (0x984 + (n) * 0x1000)
+#define MVEBU_COMPHY_DLT_CTRL_DTL_FLOOP_EN BIT(2)
+#define MVEBU_COMPHY_FRAME_DETECT0(n) (0xa14 + (n) * 0x1000)
+#define MVEBU_COMPHY_FRAME_DETECT0_PATN(n) ((n) << 7)
+#define MVEBU_COMPHY_FRAME_DETECT3(n) (0xa20 + (n) * 0x1000)
+#define MVEBU_COMPHY_FRAME_DETECT3_LOST_TIMEOUT_EN BIT(12)
+#define MVEBU_COMPHY_DME(n) (0xa28 + (n) * 0x1000)
+#define MVEBU_COMPHY_DME_ETH_MODE BIT(7)
+#define MVEBU_COMPHY_TRAINING0(n) (0xa68 + (n) * 0x1000)
+#define MVEBU_COMPHY_TRAINING0_P2P_HOLD BIT(15)
+#define MVEBU_COMPHY_TRAINING5(n) (0xaa4 + (n) * 0x1000)
+#define MVEBU_COMPHY_TRAINING5_RX_TIMER(n) ((n) << 0)
+#define MVEBU_COMPHY_TX_TRAIN_PRESET(n) (0xb1c + (n) * 0x1000)
+#define MVEBU_COMPHY_TX_TRAIN_PRESET_16B_AUTO_EN BIT(8)
+#define MVEBU_COMPHY_TX_TRAIN_PRESET_PRBS11 BIT(9)
+#define MVEBU_COMPHY_GEN1_S3(n) (0xc40 + (n) * 0x1000)
+#define MVEBU_COMPHY_GEN1_S3_FBCK_SEL BIT(9)
+#define MVEBU_COMPHY_GEN1_S4(n) (0xc44 + (n) * 0x1000)
+#define MVEBU_COMPHY_GEN1_S4_DFE_RES(n) ((n) << 8)
+#define MVEBU_COMPHY_TX_PRESET(n) (0xc68 + (n) * 0x1000)
+#define MVEBU_COMPHY_TX_PRESET_INDEX(n) ((n) << 0)
+#define MVEBU_COMPHY_GEN1_S5(n) (0xd38 + (n) * 0x1000)
+#define MVEBU_COMPHY_GEN1_S5_ICP(n) ((n) << 0)
+
+/* Relative to priv->regmap */
+#define MVEBU_COMPHY_CONF1(n) (0x1000 + (n) * 0x28)
+#define MVEBU_COMPHY_CONF1_PWRUP BIT(1)
+#define MVEBU_COMPHY_CONF1_USB_PCIE BIT(2) /* 0: Ethernet/SATA */
+#define MVEBU_COMPHY_CONF6(n) (0x1014 + (n) * 0x28)
+#define MVEBU_COMPHY_CONF6_40B BIT(18)
+#define MVEBU_COMPHY_SELECTOR 0x1140
+#define MVEBU_COMPHY_SELECTOR_PHY(n) ((n) * 0x4)
+#define MVEBU_COMPHY_PIPE_SELECTOR 0x1144
+#define MVEBU_COMPHY_PIPE_SELECTOR_PIPE(n) ((n) * 0x4)
+
+#define MVEBU_COMPHY_LANES 6
+#define MVEBU_COMPHY_PORTS 3
+
+struct mvebu_comhy_conf {
+ enum phy_mode mode;
+ unsigned lane;
+ unsigned port;
+ u32 mux;
+};
+
+#define MVEBU_COMPHY_CONF(_lane, _port, _mode, _mux) \
+ { \
+ .lane = _lane, \
+ .port = _port, \
+ .mode = _mode, \
+ .mux = _mux, \
+ }
+
+static const struct mvebu_comhy_conf mvebu_comphy_cp110_modes[] = {
+ /* lane 0 */
+ MVEBU_COMPHY_CONF(0, 1, PHY_MODE_SGMII, 0x1),
+ MVEBU_COMPHY_CONF(0, 1, PHY_MODE_2500SGMII, 0x1),
+ /* lane 1 */
+ MVEBU_COMPHY_CONF(1, 2, PHY_MODE_SGMII, 0x1),
+ MVEBU_COMPHY_CONF(1, 2, PHY_MODE_2500SGMII, 0x1),
+ /* lane 2 */
+ MVEBU_COMPHY_CONF(2, 0, PHY_MODE_SGMII, 0x1),
+ MVEBU_COMPHY_CONF(2, 0, PHY_MODE_2500SGMII, 0x1),
+ MVEBU_COMPHY_CONF(2, 0, PHY_MODE_10GKR, 0x1),
+ /* lane 3 */
+ MVEBU_COMPHY_CONF(3, 1, PHY_MODE_SGMII, 0x2),
+ MVEBU_COMPHY_CONF(3, 1, PHY_MODE_2500SGMII, 0x2),
+ /* lane 4 */
+ MVEBU_COMPHY_CONF(4, 0, PHY_MODE_SGMII, 0x2),
+ MVEBU_COMPHY_CONF(4, 0, PHY_MODE_2500SGMII, 0x2),
+ MVEBU_COMPHY_CONF(4, 0, PHY_MODE_10GKR, 0x2),
+ MVEBU_COMPHY_CONF(4, 1, PHY_MODE_SGMII, 0x1),
+ /* lane 5 */
+ MVEBU_COMPHY_CONF(5, 2, PHY_MODE_SGMII, 0x1),
+ MVEBU_COMPHY_CONF(5, 2, PHY_MODE_2500SGMII, 0x1),
+};
+
+struct mvebu_comphy_priv {
+ void __iomem *base;
+ struct regmap *regmap;
+ struct device *dev;
+};
+
+struct mvebu_comphy_lane {
+ struct mvebu_comphy_priv *priv;
+ unsigned id;
+ enum phy_mode mode;
+ int port;
+};
+
+static int mvebu_comphy_get_mux(int lane, int port, enum phy_mode mode)
+{
+ int i, n = ARRAY_SIZE(mvebu_comphy_cp110_modes);
+
+ /* Unused PHY mux value is 0x0 */
+ if (mode == PHY_MODE_INVALID)
+ return 0;
+
+ for (i = 0; i < n; i++) {
+ if (mvebu_comphy_cp110_modes[i].lane == lane &&
+ mvebu_comphy_cp110_modes[i].port == port &&
+ mvebu_comphy_cp110_modes[i].mode == mode)
+ break;
+ }
+
+ if (i == n)
+ return -EINVAL;
+
+ return mvebu_comphy_cp110_modes[i].mux;
+}
+
+static void mvebu_comphy_ethernet_init_reset(struct mvebu_comphy_lane *lane,
+ enum phy_mode mode)
+{
+ struct mvebu_comphy_priv *priv = lane->priv;
+ u32 val;
+
+ regmap_read(priv->regmap, MVEBU_COMPHY_CONF1(lane->id), &val);
+ val &= ~MVEBU_COMPHY_CONF1_USB_PCIE;
+ val |= MVEBU_COMPHY_CONF1_PWRUP;
+ regmap_write(priv->regmap, MVEBU_COMPHY_CONF1(lane->id), val);
+
+ /* Select baud rates and PLLs */
+ val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG0(lane->id));
+ val &= ~(MVEBU_COMPHY_SERDES_CFG0_PU_PLL |
+ MVEBU_COMPHY_SERDES_CFG0_PU_RX |
+ MVEBU_COMPHY_SERDES_CFG0_PU_TX |
+ MVEBU_COMPHY_SERDES_CFG0_HALF_BUS |
+ MVEBU_COMPHY_SERDES_CFG0_GEN_RX(0xf) |
+ MVEBU_COMPHY_SERDES_CFG0_GEN_TX(0xf));
+ if (mode == PHY_MODE_10GKR)
+ val |= MVEBU_COMPHY_SERDES_CFG0_GEN_RX(0xe) |
+ MVEBU_COMPHY_SERDES_CFG0_GEN_TX(0xe);
+ else if (mode == PHY_MODE_2500SGMII)
+ val |= MVEBU_COMPHY_SERDES_CFG0_GEN_RX(0x8) |
+ MVEBU_COMPHY_SERDES_CFG0_GEN_TX(0x8) |
+ MVEBU_COMPHY_SERDES_CFG0_HALF_BUS;
+ else if (mode == PHY_MODE_SGMII)
+ val |= MVEBU_COMPHY_SERDES_CFG0_GEN_RX(0x6) |
+ MVEBU_COMPHY_SERDES_CFG0_GEN_TX(0x6) |
+ MVEBU_COMPHY_SERDES_CFG0_HALF_BUS;
+ writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG0(lane->id));
+
+ /* reset */
+ val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id));
+ val &= ~(MVEBU_COMPHY_SERDES_CFG1_RESET |
+ MVEBU_COMPHY_SERDES_CFG1_CORE_RESET |
+ MVEBU_COMPHY_SERDES_CFG1_RF_RESET);
+ writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id));
+
+ /* de-assert reset */
+ val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id));
+ val |= MVEBU_COMPHY_SERDES_CFG1_RESET |
+ MVEBU_COMPHY_SERDES_CFG1_CORE_RESET;
+ writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id));
+
+ /* wait until clocks are ready */
+ mdelay(1);
+
+ /* exlicitly disable 40B, the bits isn't clear on reset */
+ regmap_read(priv->regmap, MVEBU_COMPHY_CONF6(lane->id), &val);
+ val &= ~MVEBU_COMPHY_CONF6_40B;
+ regmap_write(priv->regmap, MVEBU_COMPHY_CONF6(lane->id), val);
+
+ /* refclk selection */
+ val = readl(priv->base + MVEBU_COMPHY_MISC_CTRL0(lane->id));
+ val &= ~MVEBU_COMPHY_MISC_CTRL0_REFCLK_SEL;
+ if (mode == PHY_MODE_10GKR)
+ val |= MVEBU_COMPHY_MISC_CTRL0_ICP_FORCE;
+ writel(val, priv->base + MVEBU_COMPHY_MISC_CTRL0(lane->id));
+
+ /* power and pll selection */
+ val = readl(priv->base + MVEBU_COMPHY_PWRPLL_CTRL(lane->id));
+ val &= ~(MVEBU_COMPHY_PWRPLL_CTRL_RFREQ(0x1f) |
+ MVEBU_COMPHY_PWRPLL_PHY_MODE(0x7));
+ val |= MVEBU_COMPHY_PWRPLL_CTRL_RFREQ(0x1) |
+ MVEBU_COMPHY_PWRPLL_PHY_MODE(0x4);
+ writel(val, priv->base + MVEBU_COMPHY_PWRPLL_CTRL(lane->id));
+
+ val = readl(priv->base + MVEBU_COMPHY_LOOPBACK(lane->id));
+ val &= ~MVEBU_COMPHY_LOOPBACK_DBUS_WIDTH(0x7);
+ val |= MVEBU_COMPHY_LOOPBACK_DBUS_WIDTH(0x1);
+ writel(val, priv->base + MVEBU_COMPHY_LOOPBACK(lane->id));
+}
+
+static int mvebu_comphy_init_plls(struct mvebu_comphy_lane *lane,
+ enum phy_mode mode)
+{
+ struct mvebu_comphy_priv *priv = lane->priv;
+ u32 val;
+
+ /* SERDES external config */
+ val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG0(lane->id));
+ val |= MVEBU_COMPHY_SERDES_CFG0_PU_PLL |
+ MVEBU_COMPHY_SERDES_CFG0_PU_RX |
+ MVEBU_COMPHY_SERDES_CFG0_PU_TX;
+ writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG0(lane->id));
+
+ /* check rx/tx pll */
+ readl_poll_timeout(priv->base + MVEBU_COMPHY_SERDES_STATUS0(lane->id),
+ val,
+ val & (MVEBU_COMPHY_SERDES_STATUS0_RX_PLL_RDY |
+ MVEBU_COMPHY_SERDES_STATUS0_TX_PLL_RDY),
+ 1000, 150000);
+ if (!(val & (MVEBU_COMPHY_SERDES_STATUS0_RX_PLL_RDY |
+ MVEBU_COMPHY_SERDES_STATUS0_TX_PLL_RDY)))
+ return -ETIMEDOUT;
+
+ /* rx init */
+ val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id));
+ val |= MVEBU_COMPHY_SERDES_CFG1_RX_INIT;
+ writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id));
+
+ /* check rx */
+ readl_poll_timeout(priv->base + MVEBU_COMPHY_SERDES_STATUS0(lane->id),
+ val, val & MVEBU_COMPHY_SERDES_STATUS0_RX_INIT,
+ 1000, 10000);
+ if (!(val & MVEBU_COMPHY_SERDES_STATUS0_RX_INIT))
+ return -ETIMEDOUT;
+
+ val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id));
+ val &= ~MVEBU_COMPHY_SERDES_CFG1_RX_INIT;
+ writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id));
+
+ return 0;
+}
+
+static int mvebu_comphy_set_mode_sgmii(struct phy *phy, enum phy_mode mode)
+{
+ struct mvebu_comphy_lane *lane = phy_get_drvdata(phy);
+ struct mvebu_comphy_priv *priv = lane->priv;
+ u32 val;
+
+ mvebu_comphy_ethernet_init_reset(lane, mode);
+
+ val = readl(priv->base + MVEBU_COMPHY_RX_CTRL1(lane->id));
+ val &= ~MVEBU_COMPHY_RX_CTRL1_CLK8T_EN;
+ val |= MVEBU_COMPHY_RX_CTRL1_RXCLK2X_SEL;
+ writel(val, priv->base + MVEBU_COMPHY_RX_CTRL1(lane->id));
+
+ val = readl(priv->base + MVEBU_COMPHY_DLT_CTRL(lane->id));
+ val &= ~MVEBU_COMPHY_DLT_CTRL_DTL_FLOOP_EN;
+ writel(val, priv->base + MVEBU_COMPHY_DLT_CTRL(lane->id));
+
+ regmap_read(priv->regmap, MVEBU_COMPHY_CONF1(lane->id), &val);
+ val &= ~MVEBU_COMPHY_CONF1_USB_PCIE;
+ val |= MVEBU_COMPHY_CONF1_PWRUP;
+ regmap_write(priv->regmap, MVEBU_COMPHY_CONF1(lane->id), val);
+
+ val = readl(priv->base + MVEBU_COMPHY_GEN1_S0(lane->id));
+ val &= ~MVEBU_COMPHY_GEN1_S0_TX_EMPH(0xf);
+ val |= MVEBU_COMPHY_GEN1_S0_TX_EMPH(0x1);
+ writel(val, priv->base + MVEBU_COMPHY_GEN1_S0(lane->id));
+
+ return mvebu_comphy_init_plls(lane, PHY_MODE_SGMII);
+}
+
+static int mvebu_comphy_set_mode_10gkr(struct phy *phy)
+{
+ struct mvebu_comphy_lane *lane = phy_get_drvdata(phy);
+ struct mvebu_comphy_priv *priv = lane->priv;
+ u32 val;
+
+ mvebu_comphy_ethernet_init_reset(lane, PHY_MODE_10GKR);
+
+ val = readl(priv->base + MVEBU_COMPHY_RX_CTRL1(lane->id));
+ val |= MVEBU_COMPHY_RX_CTRL1_RXCLK2X_SEL |
+ MVEBU_COMPHY_RX_CTRL1_CLK8T_EN;
+ writel(val, priv->base + MVEBU_COMPHY_RX_CTRL1(lane->id));
+
+ val = readl(priv->base + MVEBU_COMPHY_DLT_CTRL(lane->id));
+ val |= MVEBU_COMPHY_DLT_CTRL_DTL_FLOOP_EN;
+ writel(val, priv->base + MVEBU_COMPHY_DLT_CTRL(lane->id));
+
+ /* Speed divider */
+ val = readl(priv->base + MVEBU_COMPHY_SPEED_DIV(lane->id));
+ val |= MVEBU_COMPHY_SPEED_DIV_TX_FORCE;
+ writel(val, priv->base + MVEBU_COMPHY_SPEED_DIV(lane->id));
+
+ val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG2(lane->id));
+ val |= MVEBU_COMPHY_SERDES_CFG2_DFE_EN;
+ writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG2(lane->id));
+
+ /* DFE resolution */
+ val = readl(priv->base + MVEBU_COMPHY_DFE_RES(lane->id));
+ val |= MVEBU_COMPHY_DFE_RES_FORCE_GEN_TBL;
+ writel(val, priv->base + MVEBU_COMPHY_DFE_RES(lane->id));
+
+ val = readl(priv->base + MVEBU_COMPHY_GEN1_S0(lane->id));
+ val &= ~(MVEBU_COMPHY_GEN1_S0_TX_AMP(0x1f) |
+ MVEBU_COMPHY_GEN1_S0_TX_EMPH(0xf));
+ val |= MVEBU_COMPHY_GEN1_S0_TX_AMP(0x1c) |
+ MVEBU_COMPHY_GEN1_S0_TX_EMPH(0xe);
+ writel(val, priv->base + MVEBU_COMPHY_GEN1_S0(lane->id));
+
+ val = readl(priv->base + MVEBU_COMPHY_GEN1_S2(lane->id));
+ val &= ~MVEBU_COMPHY_GEN1_S2_TX_EMPH(0xf);
+ val |= MVEBU_COMPHY_GEN1_S2_TX_EMPH_EN;
+ writel(val, priv->base + MVEBU_COMPHY_GEN1_S2(lane->id));
+
+ val = readl(priv->base + MVEBU_COMPHY_TX_SLEW_RATE(lane->id));
+ val |= MVEBU_COMPHY_TX_SLEW_RATE_EMPH(0x3) |
+ MVEBU_COMPHY_TX_SLEW_RATE_SLC(0x3f);
+ writel(val, priv->base + MVEBU_COMPHY_TX_SLEW_RATE(lane->id));
+
+ /* Impedance calibration */
+ val = readl(priv->base + MVEBU_COMPHY_IMP_CAL(lane->id));
+ val &= ~MVEBU_COMPHY_IMP_CAL_TX_EXT(0x1f);
+ val |= MVEBU_COMPHY_IMP_CAL_TX_EXT(0xe) |
+ MVEBU_COMPHY_IMP_CAL_TX_EXT_EN;
+ writel(val, priv->base + MVEBU_COMPHY_IMP_CAL(lane->id));
+
+ val = readl(priv->base + MVEBU_COMPHY_GEN1_S5(lane->id));
+ val &= ~MVEBU_COMPHY_GEN1_S5_ICP(0xf);
+ writel(val, priv->base + MVEBU_COMPHY_GEN1_S5(lane->id));
+
+ val = readl(priv->base + MVEBU_COMPHY_GEN1_S1(lane->id));
+ val &= ~(MVEBU_COMPHY_GEN1_S1_RX_MUL_PI(0x7) |
+ MVEBU_COMPHY_GEN1_S1_RX_MUL_PF(0x7) |
+ MVEBU_COMPHY_GEN1_S1_RX_MUL_FI(0x3) |
+ MVEBU_COMPHY_GEN1_S1_RX_MUL_FF(0x3));
+ val |= MVEBU_COMPHY_GEN1_S1_RX_DFE_EN |
+ MVEBU_COMPHY_GEN1_S1_RX_MUL_PI(0x2) |
+ MVEBU_COMPHY_GEN1_S1_RX_MUL_PF(0x2) |
+ MVEBU_COMPHY_GEN1_S1_RX_MUL_FF(0x1) |
+ MVEBU_COMPHY_GEN1_S1_RX_DIV(0x3);
+ writel(val, priv->base + MVEBU_COMPHY_GEN1_S1(lane->id));
+
+ val = readl(priv->base + MVEBU_COMPHY_COEF(lane->id));
+ val &= ~(MVEBU_COMPHY_COEF_DFE_EN | MVEBU_COMPHY_COEF_DFE_CTRL);
+ writel(val, priv->base + MVEBU_COMPHY_COEF(lane->id));
+
+ val = readl(priv->base + MVEBU_COMPHY_GEN1_S4(lane->id));
+ val &= ~MVEBU_COMPHY_GEN1_S4_DFE_RES(0x3);
+ val |= MVEBU_COMPHY_GEN1_S4_DFE_RES(0x1);
+ writel(val, priv->base + MVEBU_COMPHY_GEN1_S4(lane->id));
+
+ val = readl(priv->base + MVEBU_COMPHY_GEN1_S3(lane->id));
+ val |= MVEBU_COMPHY_GEN1_S3_FBCK_SEL;
+ writel(val, priv->base + MVEBU_COMPHY_GEN1_S3(lane->id));
+
+ /* rx training timer */
+ val = readl(priv->base + MVEBU_COMPHY_TRAINING5(lane->id));
+ val &= ~MVEBU_COMPHY_TRAINING5_RX_TIMER(0x3ff);
+ val |= MVEBU_COMPHY_TRAINING5_RX_TIMER(0x13);
+ writel(val, priv->base + MVEBU_COMPHY_TRAINING5(lane->id));
+
+ /* tx train peak to peak hold */
+ val = readl(priv->base + MVEBU_COMPHY_TRAINING0(lane->id));
+ val |= MVEBU_COMPHY_TRAINING0_P2P_HOLD;
+ writel(val, priv->base + MVEBU_COMPHY_TRAINING0(lane->id));
+
+ val = readl(priv->base + MVEBU_COMPHY_TX_PRESET(lane->id));
+ val &= ~MVEBU_COMPHY_TX_PRESET_INDEX(0xf);
+ val |= MVEBU_COMPHY_TX_PRESET_INDEX(0x2); /* preset coeff */
+ writel(val, priv->base + MVEBU_COMPHY_TX_PRESET(lane->id));
+
+ val = readl(priv->base + MVEBU_COMPHY_FRAME_DETECT3(lane->id));
+ val &= ~MVEBU_COMPHY_FRAME_DETECT3_LOST_TIMEOUT_EN;
+ writel(val, priv->base + MVEBU_COMPHY_FRAME_DETECT3(lane->id));
+
+ val = readl(priv->base + MVEBU_COMPHY_TX_TRAIN_PRESET(lane->id));
+ val |= MVEBU_COMPHY_TX_TRAIN_PRESET_16B_AUTO_EN |
+ MVEBU_COMPHY_TX_TRAIN_PRESET_PRBS11;
+ writel(val, priv->base + MVEBU_COMPHY_TX_TRAIN_PRESET(lane->id));
+
+ val = readl(priv->base + MVEBU_COMPHY_FRAME_DETECT0(lane->id));
+ val &= ~MVEBU_COMPHY_FRAME_DETECT0_PATN(0x1ff);
+ val |= MVEBU_COMPHY_FRAME_DETECT0_PATN(0x88);
+ writel(val, priv->base + MVEBU_COMPHY_FRAME_DETECT0(lane->id));
+
+ val = readl(priv->base + MVEBU_COMPHY_DME(lane->id));
+ val |= MVEBU_COMPHY_DME_ETH_MODE;
+ writel(val, priv->base + MVEBU_COMPHY_DME(lane->id));
+
+ val = readl(priv->base + MVEBU_COMPHY_VDD_CAL0(lane->id));
+ val |= MVEBU_COMPHY_VDD_CAL0_CONT_MODE;
+ writel(val, priv->base + MVEBU_COMPHY_VDD_CAL0(lane->id));
+
+ val = readl(priv->base + MVEBU_SP_CALIB(lane->id));
+ val &= ~MVEBU_SP_CALIB_SAMPLER(0x3);
+ val |= MVEBU_SP_CALIB_SAMPLER(0x3) |
+ MVEBU_SP_CALIB_SAMPLER_EN;
+ writel(val, priv->base + MVEBU_SP_CALIB(lane->id));
+ val &= ~MVEBU_SP_CALIB_SAMPLER_EN;
+ writel(val, priv->base + MVEBU_SP_CALIB(lane->id));
+
+ /* External rx regulator */
+ val = readl(priv->base + MVEBU_COMPHY_EXT_SELV(lane->id));
+ val &= ~MVEBU_COMPHY_EXT_SELV_RX_SAMPL(0x1f);
+ val |= MVEBU_COMPHY_EXT_SELV_RX_SAMPL(0x1a);
+ writel(val, priv->base + MVEBU_COMPHY_EXT_SELV(lane->id));
+
+ return mvebu_comphy_init_plls(lane, PHY_MODE_10GKR);
+}
+
+static int mvebu_comphy_power_on(struct phy *phy)
+{
+ struct mvebu_comphy_lane *lane = phy_get_drvdata(phy);
+ struct mvebu_comphy_priv *priv = lane->priv;
+ int ret, mux;
+ u32 val;
+
+ mux = mvebu_comphy_get_mux(lane->id, lane->port, lane->mode);
+ if (mux < 0)
+ return -ENOTSUPP;
+
+ regmap_read(priv->regmap, MVEBU_COMPHY_PIPE_SELECTOR, &val);
+ val &= ~(0xf << MVEBU_COMPHY_PIPE_SELECTOR_PIPE(lane->id));
+ regmap_write(priv->regmap, MVEBU_COMPHY_PIPE_SELECTOR, val);
+
+ regmap_read(priv->regmap, MVEBU_COMPHY_SELECTOR, &val);
+ val &= ~(0xf << MVEBU_COMPHY_SELECTOR_PHY(lane->id));
+ val |= mux << MVEBU_COMPHY_SELECTOR_PHY(lane->id);
+ regmap_write(priv->regmap, MVEBU_COMPHY_SELECTOR, val);
+
+ switch (lane->mode) {
+ case PHY_MODE_SGMII:
+ case PHY_MODE_2500SGMII:
+ ret = mvebu_comphy_set_mode_sgmii(phy, lane->mode);
+ break;
+ case PHY_MODE_10GKR:
+ ret = mvebu_comphy_set_mode_10gkr(phy);
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ /* digital reset */
+ val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id));
+ val |= MVEBU_COMPHY_SERDES_CFG1_RF_RESET;
+ writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id));
+
+ return ret;
+}
+
+static int mvebu_comphy_set_mode(struct phy *phy, enum phy_mode mode)
+{
+ struct mvebu_comphy_lane *lane = phy_get_drvdata(phy);
+
+ if (mvebu_comphy_get_mux(lane->id, lane->port, mode) < 0)
+ return -EINVAL;
+
+ lane->mode = mode;
+ return 0;
+}
+
+static int mvebu_comphy_power_off(struct phy *phy)
+{
+ struct mvebu_comphy_lane *lane = phy_get_drvdata(phy);
+ struct mvebu_comphy_priv *priv = lane->priv;
+ u32 val;
+
+ val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id));
+ val &= ~(MVEBU_COMPHY_SERDES_CFG1_RESET |
+ MVEBU_COMPHY_SERDES_CFG1_CORE_RESET |
+ MVEBU_COMPHY_SERDES_CFG1_RF_RESET);
+ writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id));
+
+ regmap_read(priv->regmap, MVEBU_COMPHY_SELECTOR, &val);
+ val &= ~(0xf << MVEBU_COMPHY_SELECTOR_PHY(lane->id));
+ regmap_write(priv->regmap, MVEBU_COMPHY_SELECTOR, val);
+
+ regmap_read(priv->regmap, MVEBU_COMPHY_PIPE_SELECTOR, &val);
+ val &= ~(0xf << MVEBU_COMPHY_PIPE_SELECTOR_PIPE(lane->id));
+ regmap_write(priv->regmap, MVEBU_COMPHY_PIPE_SELECTOR, val);
+
+ return 0;
+}
+
+static const struct phy_ops mvebu_comphy_ops = {
+ .power_on = mvebu_comphy_power_on,
+ .power_off = mvebu_comphy_power_off,
+ .set_mode = mvebu_comphy_set_mode,
+ .owner = THIS_MODULE,
+};
+
+static struct phy *mvebu_comphy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct mvebu_comphy_lane *lane;
+ struct phy *phy;
+
+ if (WARN_ON(args->args[0] >= MVEBU_COMPHY_PORTS))
+ return ERR_PTR(-EINVAL);
+
+ phy = of_phy_simple_xlate(dev, args);
+ if (IS_ERR(phy))
+ return phy;
+
+ lane = phy_get_drvdata(phy);
+ if (lane->port >= 0)
+ return ERR_PTR(-EBUSY);
+ lane->port = args->args[0];
+
+ return phy;
+}
+
+static int mvebu_comphy_probe(struct platform_device *pdev)
+{
+ struct mvebu_comphy_priv *priv;
+ struct phy_provider *provider;
+ struct device_node *child;
+ struct resource *res;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = &pdev->dev;
+ priv->regmap =
+ syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+ "marvell,system-controller");
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ for_each_available_child_of_node(pdev->dev.of_node, child) {
+ struct mvebu_comphy_lane *lane;
+ struct phy *phy;
+ int ret;
+ u32 val;
+
+ ret = of_property_read_u32(child, "reg", &val);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "missing 'reg' property (%d)\n",
+ ret);
+ continue;
+ }
+
+ if (val >= MVEBU_COMPHY_LANES) {
+ dev_err(&pdev->dev, "invalid 'reg' property\n");
+ continue;
+ }
+
+ lane = devm_kzalloc(&pdev->dev, sizeof(*lane), GFP_KERNEL);
+ if (!lane)
+ return -ENOMEM;
+
+ phy = devm_phy_create(&pdev->dev, child, &mvebu_comphy_ops);
+ if (IS_ERR(phy))
+ return PTR_ERR(phy);
+
+ lane->priv = priv;
+ lane->mode = PHY_MODE_INVALID;
+ lane->id = val;
+ lane->port = -1;
+ phy_set_drvdata(phy, lane);
+
+ /*
+ * Once all modes are supported in this driver we should call
+ * mvebu_comphy_power_off(phy) here to avoid relying on the
+ * bootloader/firmware configuration.
+ */
+ }
+
+ dev_set_drvdata(&pdev->dev, priv);
+ provider = devm_of_phy_provider_register(&pdev->dev,
+ mvebu_comphy_xlate);
+ return PTR_ERR_OR_ZERO(provider);
+}
+
+static const struct of_device_id mvebu_comphy_of_match_table[] = {
+ { .compatible = "marvell,comphy-cp110" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, mvebu_comphy_of_match_table);
+
+static struct platform_driver mvebu_comphy_driver = {
+ .probe = mvebu_comphy_probe,
+ .driver = {
+ .name = "mvebu-comphy",
+ .of_match_table = mvebu_comphy_of_match_table,
+ },
+};
+module_platform_driver(mvebu_comphy_driver);
+
+MODULE_AUTHOR("Antoine Tenart <antoine.tenart@free-electrons.com>");
+MODULE_DESCRIPTION("Common PHY driver for mvebu SoCs");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/marvell/phy-mvebu-sata.c b/drivers/phy/marvell/phy-mvebu-sata.c
new file mode 100644
index 000000000..768ce92e8
--- /dev/null
+++ b/drivers/phy/marvell/phy-mvebu-sata.c
@@ -0,0 +1,138 @@
+/*
+ * phy-mvebu-sata.c: SATA Phy driver for the Marvell mvebu SoCs.
+ *
+ * Copyright (C) 2013 Andrew Lunn <andrew@lunn.ch>
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/phy/phy.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+
+struct priv {
+ struct clk *clk;
+ void __iomem *base;
+};
+
+#define SATA_PHY_MODE_2 0x0330
+#define MODE_2_FORCE_PU_TX BIT(0)
+#define MODE_2_FORCE_PU_RX BIT(1)
+#define MODE_2_PU_PLL BIT(2)
+#define MODE_2_PU_IVREF BIT(3)
+#define SATA_IF_CTRL 0x0050
+#define CTRL_PHY_SHUTDOWN BIT(9)
+
+static int phy_mvebu_sata_power_on(struct phy *phy)
+{
+ struct priv *priv = phy_get_drvdata(phy);
+ u32 reg;
+
+ clk_prepare_enable(priv->clk);
+
+ /* Enable PLL and IVREF */
+ reg = readl(priv->base + SATA_PHY_MODE_2);
+ reg |= (MODE_2_FORCE_PU_TX | MODE_2_FORCE_PU_RX |
+ MODE_2_PU_PLL | MODE_2_PU_IVREF);
+ writel(reg , priv->base + SATA_PHY_MODE_2);
+
+ /* Enable PHY */
+ reg = readl(priv->base + SATA_IF_CTRL);
+ reg &= ~CTRL_PHY_SHUTDOWN;
+ writel(reg, priv->base + SATA_IF_CTRL);
+
+ clk_disable_unprepare(priv->clk);
+
+ return 0;
+}
+
+static int phy_mvebu_sata_power_off(struct phy *phy)
+{
+ struct priv *priv = phy_get_drvdata(phy);
+ u32 reg;
+
+ clk_prepare_enable(priv->clk);
+
+ /* Disable PLL and IVREF */
+ reg = readl(priv->base + SATA_PHY_MODE_2);
+ reg &= ~(MODE_2_FORCE_PU_TX | MODE_2_FORCE_PU_RX |
+ MODE_2_PU_PLL | MODE_2_PU_IVREF);
+ writel(reg, priv->base + SATA_PHY_MODE_2);
+
+ /* Disable PHY */
+ reg = readl(priv->base + SATA_IF_CTRL);
+ reg |= CTRL_PHY_SHUTDOWN;
+ writel(reg, priv->base + SATA_IF_CTRL);
+
+ clk_disable_unprepare(priv->clk);
+
+ return 0;
+}
+
+static const struct phy_ops phy_mvebu_sata_ops = {
+ .power_on = phy_mvebu_sata_power_on,
+ .power_off = phy_mvebu_sata_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int phy_mvebu_sata_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct resource *res;
+ struct priv *priv;
+ struct phy *phy;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ priv->clk = devm_clk_get(&pdev->dev, "sata");
+ if (IS_ERR(priv->clk))
+ return PTR_ERR(priv->clk);
+
+ phy = devm_phy_create(&pdev->dev, NULL, &phy_mvebu_sata_ops);
+ if (IS_ERR(phy))
+ return PTR_ERR(phy);
+
+ phy_set_drvdata(phy, priv);
+
+ phy_provider = devm_of_phy_provider_register(&pdev->dev,
+ of_phy_simple_xlate);
+ if (IS_ERR(phy_provider))
+ return PTR_ERR(phy_provider);
+
+ /* The boot loader may of left it on. Turn it off. */
+ phy_mvebu_sata_power_off(phy);
+
+ return 0;
+}
+
+static const struct of_device_id phy_mvebu_sata_of_match[] = {
+ { .compatible = "marvell,mvebu-sata-phy" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, phy_mvebu_sata_of_match);
+
+static struct platform_driver phy_mvebu_sata_driver = {
+ .probe = phy_mvebu_sata_probe,
+ .driver = {
+ .name = "phy-mvebu-sata",
+ .of_match_table = phy_mvebu_sata_of_match,
+ }
+};
+module_platform_driver(phy_mvebu_sata_driver);
+
+MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
+MODULE_DESCRIPTION("Marvell MVEBU SATA PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/marvell/phy-pxa-28nm-hsic.c b/drivers/phy/marvell/phy-pxa-28nm-hsic.c
new file mode 100644
index 000000000..234aacf4d
--- /dev/null
+++ b/drivers/phy/marvell/phy-pxa-28nm-hsic.c
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2015 Linaro, Ltd.
+ * Rob Herring <robh@kernel.org>
+ *
+ * Based on vendor driver:
+ * Copyright (C) 2013 Marvell Inc.
+ * Author: Chao Xie <xiechao.mail@gmail.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.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+
+#define PHY_28NM_HSIC_CTRL 0x08
+#define PHY_28NM_HSIC_IMPCAL_CAL 0x18
+#define PHY_28NM_HSIC_PLL_CTRL01 0x1c
+#define PHY_28NM_HSIC_PLL_CTRL2 0x20
+#define PHY_28NM_HSIC_INT 0x28
+
+#define PHY_28NM_HSIC_PLL_SELLPFR_SHIFT 26
+#define PHY_28NM_HSIC_PLL_FBDIV_SHIFT 0
+#define PHY_28NM_HSIC_PLL_REFDIV_SHIFT 9
+
+#define PHY_28NM_HSIC_S2H_PU_PLL BIT(10)
+#define PHY_28NM_HSIC_H2S_PLL_LOCK BIT(15)
+#define PHY_28NM_HSIC_S2H_HSIC_EN BIT(7)
+#define S2H_DRV_SE0_4RESUME BIT(14)
+#define PHY_28NM_HSIC_H2S_IMPCAL_DONE BIT(27)
+
+#define PHY_28NM_HSIC_CONNECT_INT BIT(1)
+#define PHY_28NM_HSIC_HS_READY_INT BIT(2)
+
+struct mv_hsic_phy {
+ struct phy *phy;
+ struct platform_device *pdev;
+ void __iomem *base;
+ struct clk *clk;
+};
+
+static bool wait_for_reg(void __iomem *reg, u32 mask, unsigned long timeout)
+{
+ timeout += jiffies;
+ while (time_is_after_eq_jiffies(timeout)) {
+ if ((readl(reg) & mask) == mask)
+ return true;
+ msleep(1);
+ }
+ return false;
+}
+
+static int mv_hsic_phy_init(struct phy *phy)
+{
+ struct mv_hsic_phy *mv_phy = phy_get_drvdata(phy);
+ struct platform_device *pdev = mv_phy->pdev;
+ void __iomem *base = mv_phy->base;
+
+ clk_prepare_enable(mv_phy->clk);
+
+ /* Set reference clock */
+ writel(0x1 << PHY_28NM_HSIC_PLL_SELLPFR_SHIFT |
+ 0xf0 << PHY_28NM_HSIC_PLL_FBDIV_SHIFT |
+ 0xd << PHY_28NM_HSIC_PLL_REFDIV_SHIFT,
+ base + PHY_28NM_HSIC_PLL_CTRL01);
+
+ /* Turn on PLL */
+ writel(readl(base + PHY_28NM_HSIC_PLL_CTRL2) |
+ PHY_28NM_HSIC_S2H_PU_PLL,
+ base + PHY_28NM_HSIC_PLL_CTRL2);
+
+ /* Make sure PHY PLL is locked */
+ if (!wait_for_reg(base + PHY_28NM_HSIC_PLL_CTRL2,
+ PHY_28NM_HSIC_H2S_PLL_LOCK, HZ / 10)) {
+ dev_err(&pdev->dev, "HSIC PHY PLL not locked after 100mS.");
+ clk_disable_unprepare(mv_phy->clk);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int mv_hsic_phy_power_on(struct phy *phy)
+{
+ struct mv_hsic_phy *mv_phy = phy_get_drvdata(phy);
+ struct platform_device *pdev = mv_phy->pdev;
+ void __iomem *base = mv_phy->base;
+ u32 reg;
+
+ reg = readl(base + PHY_28NM_HSIC_CTRL);
+ /* Avoid SE0 state when resume for some device will take it as reset */
+ reg &= ~S2H_DRV_SE0_4RESUME;
+ reg |= PHY_28NM_HSIC_S2H_HSIC_EN; /* Enable HSIC PHY */
+ writel(reg, base + PHY_28NM_HSIC_CTRL);
+
+ /*
+ * Calibration Timing
+ * ____________________________
+ * CAL START ___|
+ * ____________________
+ * CAL_DONE ___________|
+ * | 400us |
+ */
+
+ /* Make sure PHY Calibration is ready */
+ if (!wait_for_reg(base + PHY_28NM_HSIC_IMPCAL_CAL,
+ PHY_28NM_HSIC_H2S_IMPCAL_DONE, HZ / 10)) {
+ dev_warn(&pdev->dev, "HSIC PHY READY not set after 100mS.");
+ return -ETIMEDOUT;
+ }
+
+ /* Waiting for HSIC connect int*/
+ if (!wait_for_reg(base + PHY_28NM_HSIC_INT,
+ PHY_28NM_HSIC_CONNECT_INT, HZ / 5)) {
+ dev_warn(&pdev->dev, "HSIC wait for connect interrupt timeout.");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int mv_hsic_phy_power_off(struct phy *phy)
+{
+ struct mv_hsic_phy *mv_phy = phy_get_drvdata(phy);
+ void __iomem *base = mv_phy->base;
+
+ writel(readl(base + PHY_28NM_HSIC_CTRL) & ~PHY_28NM_HSIC_S2H_HSIC_EN,
+ base + PHY_28NM_HSIC_CTRL);
+
+ return 0;
+}
+
+static int mv_hsic_phy_exit(struct phy *phy)
+{
+ struct mv_hsic_phy *mv_phy = phy_get_drvdata(phy);
+ void __iomem *base = mv_phy->base;
+
+ /* Turn off PLL */
+ writel(readl(base + PHY_28NM_HSIC_PLL_CTRL2) &
+ ~PHY_28NM_HSIC_S2H_PU_PLL,
+ base + PHY_28NM_HSIC_PLL_CTRL2);
+
+ clk_disable_unprepare(mv_phy->clk);
+ return 0;
+}
+
+
+static const struct phy_ops hsic_ops = {
+ .init = mv_hsic_phy_init,
+ .power_on = mv_hsic_phy_power_on,
+ .power_off = mv_hsic_phy_power_off,
+ .exit = mv_hsic_phy_exit,
+ .owner = THIS_MODULE,
+};
+
+static int mv_hsic_phy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct mv_hsic_phy *mv_phy;
+ struct resource *r;
+
+ mv_phy = devm_kzalloc(&pdev->dev, sizeof(*mv_phy), GFP_KERNEL);
+ if (!mv_phy)
+ return -ENOMEM;
+
+ mv_phy->pdev = pdev;
+
+ mv_phy->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(mv_phy->clk)) {
+ dev_err(&pdev->dev, "failed to get clock.\n");
+ return PTR_ERR(mv_phy->clk);
+ }
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ mv_phy->base = devm_ioremap_resource(&pdev->dev, r);
+ if (IS_ERR(mv_phy->base))
+ return PTR_ERR(mv_phy->base);
+
+ mv_phy->phy = devm_phy_create(&pdev->dev, pdev->dev.of_node, &hsic_ops);
+ if (IS_ERR(mv_phy->phy))
+ return PTR_ERR(mv_phy->phy);
+
+ phy_set_drvdata(mv_phy->phy, mv_phy);
+
+ phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id mv_hsic_phy_dt_match[] = {
+ { .compatible = "marvell,pxa1928-hsic-phy", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, mv_hsic_phy_dt_match);
+
+static struct platform_driver mv_hsic_phy_driver = {
+ .probe = mv_hsic_phy_probe,
+ .driver = {
+ .name = "mv-hsic-phy",
+ .of_match_table = of_match_ptr(mv_hsic_phy_dt_match),
+ },
+};
+module_platform_driver(mv_hsic_phy_driver);
+
+MODULE_AUTHOR("Rob Herring <robh@kernel.org>");
+MODULE_DESCRIPTION("Marvell HSIC phy driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/marvell/phy-pxa-28nm-usb2.c b/drivers/phy/marvell/phy-pxa-28nm-usb2.c
new file mode 100644
index 000000000..37e9c8ca4
--- /dev/null
+++ b/drivers/phy/marvell/phy-pxa-28nm-usb2.c
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2015 Linaro, Ltd.
+ * Rob Herring <robh@kernel.org>
+ *
+ * Based on vendor driver:
+ * Copyright (C) 2013 Marvell Inc.
+ * Author: Chao Xie <xiechao.mail@gmail.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.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+
+/* USB PXA1928 PHY mapping */
+#define PHY_28NM_PLL_REG0 0x0
+#define PHY_28NM_PLL_REG1 0x4
+#define PHY_28NM_CAL_REG 0x8
+#define PHY_28NM_TX_REG0 0x0c
+#define PHY_28NM_TX_REG1 0x10
+#define PHY_28NM_RX_REG0 0x14
+#define PHY_28NM_RX_REG1 0x18
+#define PHY_28NM_DIG_REG0 0x1c
+#define PHY_28NM_DIG_REG1 0x20
+#define PHY_28NM_TEST_REG0 0x24
+#define PHY_28NM_TEST_REG1 0x28
+#define PHY_28NM_MOC_REG 0x2c
+#define PHY_28NM_PHY_RESERVE 0x30
+#define PHY_28NM_OTG_REG 0x34
+#define PHY_28NM_CHRG_DET 0x38
+#define PHY_28NM_CTRL_REG0 0xc4
+#define PHY_28NM_CTRL_REG1 0xc8
+#define PHY_28NM_CTRL_REG2 0xd4
+#define PHY_28NM_CTRL_REG3 0xdc
+
+/* PHY_28NM_PLL_REG0 */
+#define PHY_28NM_PLL_READY BIT(31)
+
+#define PHY_28NM_PLL_SELLPFR_SHIFT 28
+#define PHY_28NM_PLL_SELLPFR_MASK (0x3 << 28)
+
+#define PHY_28NM_PLL_FBDIV_SHIFT 16
+#define PHY_28NM_PLL_FBDIV_MASK (0x1ff << 16)
+
+#define PHY_28NM_PLL_ICP_SHIFT 8
+#define PHY_28NM_PLL_ICP_MASK (0x7 << 8)
+
+#define PHY_28NM_PLL_REFDIV_SHIFT 0
+#define PHY_28NM_PLL_REFDIV_MASK 0x7f
+
+/* PHY_28NM_PLL_REG1 */
+#define PHY_28NM_PLL_PU_BY_REG BIT(1)
+
+#define PHY_28NM_PLL_PU_PLL BIT(0)
+
+/* PHY_28NM_CAL_REG */
+#define PHY_28NM_PLL_PLLCAL_DONE BIT(31)
+
+#define PHY_28NM_PLL_IMPCAL_DONE BIT(23)
+
+#define PHY_28NM_PLL_KVCO_SHIFT 16
+#define PHY_28NM_PLL_KVCO_MASK (0x7 << 16)
+
+#define PHY_28NM_PLL_CAL12_SHIFT 20
+#define PHY_28NM_PLL_CAL12_MASK (0x3 << 20)
+
+#define PHY_28NM_IMPCAL_VTH_SHIFT 8
+#define PHY_28NM_IMPCAL_VTH_MASK (0x7 << 8)
+
+#define PHY_28NM_PLLCAL_START_SHIFT 22
+#define PHY_28NM_IMPCAL_START_SHIFT 13
+
+/* PHY_28NM_TX_REG0 */
+#define PHY_28NM_TX_PU_BY_REG BIT(25)
+
+#define PHY_28NM_TX_PU_ANA BIT(24)
+
+#define PHY_28NM_TX_AMP_SHIFT 20
+#define PHY_28NM_TX_AMP_MASK (0x7 << 20)
+
+/* PHY_28NM_RX_REG0 */
+#define PHY_28NM_RX_SQ_THRESH_SHIFT 0
+#define PHY_28NM_RX_SQ_THRESH_MASK (0xf << 0)
+
+/* PHY_28NM_RX_REG1 */
+#define PHY_28NM_RX_SQCAL_DONE BIT(31)
+
+/* PHY_28NM_DIG_REG0 */
+#define PHY_28NM_DIG_BITSTAFFING_ERR BIT(31)
+#define PHY_28NM_DIG_SYNC_ERR BIT(30)
+
+#define PHY_28NM_DIG_SQ_FILT_SHIFT 16
+#define PHY_28NM_DIG_SQ_FILT_MASK (0x7 << 16)
+
+#define PHY_28NM_DIG_SQ_BLK_SHIFT 12
+#define PHY_28NM_DIG_SQ_BLK_MASK (0x7 << 12)
+
+#define PHY_28NM_DIG_SYNC_NUM_SHIFT 0
+#define PHY_28NM_DIG_SYNC_NUM_MASK (0x3 << 0)
+
+#define PHY_28NM_PLL_LOCK_BYPASS BIT(7)
+
+/* PHY_28NM_OTG_REG */
+#define PHY_28NM_OTG_CONTROL_BY_PIN BIT(5)
+#define PHY_28NM_OTG_PU_OTG BIT(4)
+
+#define PHY_28NM_CHGDTC_ENABLE_SWITCH_DM_SHIFT_28 13
+#define PHY_28NM_CHGDTC_ENABLE_SWITCH_DP_SHIFT_28 12
+#define PHY_28NM_CHGDTC_VSRC_CHARGE_SHIFT_28 10
+#define PHY_28NM_CHGDTC_VDAT_CHARGE_SHIFT_28 8
+#define PHY_28NM_CHGDTC_CDP_DM_AUTO_SWITCH_SHIFT_28 7
+#define PHY_28NM_CHGDTC_DP_DM_SWAP_SHIFT_28 6
+#define PHY_28NM_CHGDTC_PU_CHRG_DTC_SHIFT_28 5
+#define PHY_28NM_CHGDTC_PD_EN_SHIFT_28 4
+#define PHY_28NM_CHGDTC_DCP_EN_SHIFT_28 3
+#define PHY_28NM_CHGDTC_CDP_EN_SHIFT_28 2
+#define PHY_28NM_CHGDTC_TESTMON_CHRGDTC_SHIFT_28 0
+
+#define PHY_28NM_CTRL1_CHRG_DTC_OUT_SHIFT_28 4
+#define PHY_28NM_CTRL1_VBUSDTC_OUT_SHIFT_28 2
+
+#define PHY_28NM_CTRL3_OVERWRITE BIT(0)
+#define PHY_28NM_CTRL3_VBUS_VALID BIT(4)
+#define PHY_28NM_CTRL3_AVALID BIT(5)
+#define PHY_28NM_CTRL3_BVALID BIT(6)
+
+struct mv_usb2_phy {
+ struct phy *phy;
+ struct platform_device *pdev;
+ void __iomem *base;
+ struct clk *clk;
+};
+
+static bool wait_for_reg(void __iomem *reg, u32 mask, unsigned long timeout)
+{
+ timeout += jiffies;
+ while (time_is_after_eq_jiffies(timeout)) {
+ if ((readl(reg) & mask) == mask)
+ return true;
+ msleep(1);
+ }
+ return false;
+}
+
+static int mv_usb2_phy_28nm_init(struct phy *phy)
+{
+ struct mv_usb2_phy *mv_phy = phy_get_drvdata(phy);
+ struct platform_device *pdev = mv_phy->pdev;
+ void __iomem *base = mv_phy->base;
+ u32 reg;
+ int ret;
+
+ clk_prepare_enable(mv_phy->clk);
+
+ /* PHY_28NM_PLL_REG0 */
+ reg = readl(base + PHY_28NM_PLL_REG0) &
+ ~(PHY_28NM_PLL_SELLPFR_MASK | PHY_28NM_PLL_FBDIV_MASK
+ | PHY_28NM_PLL_ICP_MASK | PHY_28NM_PLL_REFDIV_MASK);
+ writel(reg | (0x1 << PHY_28NM_PLL_SELLPFR_SHIFT
+ | 0xf0 << PHY_28NM_PLL_FBDIV_SHIFT
+ | 0x3 << PHY_28NM_PLL_ICP_SHIFT
+ | 0xd << PHY_28NM_PLL_REFDIV_SHIFT),
+ base + PHY_28NM_PLL_REG0);
+
+ /* PHY_28NM_PLL_REG1 */
+ reg = readl(base + PHY_28NM_PLL_REG1);
+ writel(reg | PHY_28NM_PLL_PU_PLL | PHY_28NM_PLL_PU_BY_REG,
+ base + PHY_28NM_PLL_REG1);
+
+ /* PHY_28NM_TX_REG0 */
+ reg = readl(base + PHY_28NM_TX_REG0) & ~PHY_28NM_TX_AMP_MASK;
+ writel(reg | PHY_28NM_TX_PU_BY_REG | 0x3 << PHY_28NM_TX_AMP_SHIFT |
+ PHY_28NM_TX_PU_ANA,
+ base + PHY_28NM_TX_REG0);
+
+ /* PHY_28NM_RX_REG0 */
+ reg = readl(base + PHY_28NM_RX_REG0) & ~PHY_28NM_RX_SQ_THRESH_MASK;
+ writel(reg | 0xa << PHY_28NM_RX_SQ_THRESH_SHIFT,
+ base + PHY_28NM_RX_REG0);
+
+ /* PHY_28NM_DIG_REG0 */
+ reg = readl(base + PHY_28NM_DIG_REG0) &
+ ~(PHY_28NM_DIG_BITSTAFFING_ERR | PHY_28NM_DIG_SYNC_ERR |
+ PHY_28NM_DIG_SQ_FILT_MASK | PHY_28NM_DIG_SQ_BLK_MASK |
+ PHY_28NM_DIG_SYNC_NUM_MASK);
+ writel(reg | (0x1 << PHY_28NM_DIG_SYNC_NUM_SHIFT |
+ PHY_28NM_PLL_LOCK_BYPASS),
+ base + PHY_28NM_DIG_REG0);
+
+ /* PHY_28NM_OTG_REG */
+ reg = readl(base + PHY_28NM_OTG_REG) | PHY_28NM_OTG_PU_OTG;
+ writel(reg & ~PHY_28NM_OTG_CONTROL_BY_PIN, base + PHY_28NM_OTG_REG);
+
+ /*
+ * Calibration Timing
+ * ____________________________
+ * CAL START ___|
+ * ____________________
+ * CAL_DONE ___________|
+ * | 400us |
+ */
+
+ /* Make sure PHY Calibration is ready */
+ if (!wait_for_reg(base + PHY_28NM_CAL_REG,
+ PHY_28NM_PLL_PLLCAL_DONE | PHY_28NM_PLL_IMPCAL_DONE,
+ HZ / 10)) {
+ dev_warn(&pdev->dev, "USB PHY PLL calibrate not done after 100mS.");
+ ret = -ETIMEDOUT;
+ goto err_clk;
+ }
+ if (!wait_for_reg(base + PHY_28NM_RX_REG1,
+ PHY_28NM_RX_SQCAL_DONE, HZ / 10)) {
+ dev_warn(&pdev->dev, "USB PHY RX SQ calibrate not done after 100mS.");
+ ret = -ETIMEDOUT;
+ goto err_clk;
+ }
+ /* Make sure PHY PLL is ready */
+ if (!wait_for_reg(base + PHY_28NM_PLL_REG0,
+ PHY_28NM_PLL_READY, HZ / 10)) {
+ dev_warn(&pdev->dev, "PLL_READY not set after 100mS.");
+ ret = -ETIMEDOUT;
+ goto err_clk;
+ }
+
+ return 0;
+err_clk:
+ clk_disable_unprepare(mv_phy->clk);
+ return ret;
+}
+
+static int mv_usb2_phy_28nm_power_on(struct phy *phy)
+{
+ struct mv_usb2_phy *mv_phy = phy_get_drvdata(phy);
+ void __iomem *base = mv_phy->base;
+
+ writel(readl(base + PHY_28NM_CTRL_REG3) |
+ (PHY_28NM_CTRL3_OVERWRITE | PHY_28NM_CTRL3_VBUS_VALID |
+ PHY_28NM_CTRL3_AVALID | PHY_28NM_CTRL3_BVALID),
+ base + PHY_28NM_CTRL_REG3);
+
+ return 0;
+}
+
+static int mv_usb2_phy_28nm_power_off(struct phy *phy)
+{
+ struct mv_usb2_phy *mv_phy = phy_get_drvdata(phy);
+ void __iomem *base = mv_phy->base;
+
+ writel(readl(base + PHY_28NM_CTRL_REG3) |
+ ~(PHY_28NM_CTRL3_OVERWRITE | PHY_28NM_CTRL3_VBUS_VALID
+ | PHY_28NM_CTRL3_AVALID | PHY_28NM_CTRL3_BVALID),
+ base + PHY_28NM_CTRL_REG3);
+
+ return 0;
+}
+
+static int mv_usb2_phy_28nm_exit(struct phy *phy)
+{
+ struct mv_usb2_phy *mv_phy = phy_get_drvdata(phy);
+ void __iomem *base = mv_phy->base;
+ unsigned int val;
+
+ val = readw(base + PHY_28NM_PLL_REG1);
+ val &= ~PHY_28NM_PLL_PU_PLL;
+ writew(val, base + PHY_28NM_PLL_REG1);
+
+ /* power down PHY Analog part */
+ val = readw(base + PHY_28NM_TX_REG0);
+ val &= ~PHY_28NM_TX_PU_ANA;
+ writew(val, base + PHY_28NM_TX_REG0);
+
+ /* power down PHY OTG part */
+ val = readw(base + PHY_28NM_OTG_REG);
+ val &= ~PHY_28NM_OTG_PU_OTG;
+ writew(val, base + PHY_28NM_OTG_REG);
+
+ clk_disable_unprepare(mv_phy->clk);
+ return 0;
+}
+
+static const struct phy_ops usb_ops = {
+ .init = mv_usb2_phy_28nm_init,
+ .power_on = mv_usb2_phy_28nm_power_on,
+ .power_off = mv_usb2_phy_28nm_power_off,
+ .exit = mv_usb2_phy_28nm_exit,
+ .owner = THIS_MODULE,
+};
+
+static int mv_usb2_phy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct mv_usb2_phy *mv_phy;
+ struct resource *r;
+
+ mv_phy = devm_kzalloc(&pdev->dev, sizeof(*mv_phy), GFP_KERNEL);
+ if (!mv_phy)
+ return -ENOMEM;
+
+ mv_phy->pdev = pdev;
+
+ mv_phy->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(mv_phy->clk)) {
+ dev_err(&pdev->dev, "failed to get clock.\n");
+ return PTR_ERR(mv_phy->clk);
+ }
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ mv_phy->base = devm_ioremap_resource(&pdev->dev, r);
+ if (IS_ERR(mv_phy->base))
+ return PTR_ERR(mv_phy->base);
+
+ mv_phy->phy = devm_phy_create(&pdev->dev, pdev->dev.of_node, &usb_ops);
+ if (IS_ERR(mv_phy->phy))
+ return PTR_ERR(mv_phy->phy);
+
+ phy_set_drvdata(mv_phy->phy, mv_phy);
+
+ phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id mv_usbphy_dt_match[] = {
+ { .compatible = "marvell,pxa1928-usb-phy", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, mv_usbphy_dt_match);
+
+static struct platform_driver mv_usb2_phy_driver = {
+ .probe = mv_usb2_phy_probe,
+ .driver = {
+ .name = "mv-usb2-phy",
+ .of_match_table = of_match_ptr(mv_usbphy_dt_match),
+ },
+};
+module_platform_driver(mv_usb2_phy_driver);
+
+MODULE_AUTHOR("Rob Herring <robh@kernel.org>");
+MODULE_DESCRIPTION("Marvell USB2 phy driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/mediatek/Kconfig b/drivers/phy/mediatek/Kconfig
new file mode 100644
index 000000000..8857d00b3
--- /dev/null
+++ b/drivers/phy/mediatek/Kconfig
@@ -0,0 +1,23 @@
+#
+# Phy drivers for Mediatek devices
+#
+config PHY_MTK_TPHY
+ tristate "MediaTek T-PHY Driver"
+ depends on ARCH_MEDIATEK && OF
+ select GENERIC_PHY
+ help
+ Say 'Y' here to add support for MediaTek T-PHY driver,
+ it supports multiple usb2.0, usb3.0 ports, PCIe and
+ SATA, and meanwhile supports two version T-PHY which have
+ different banks layout, the T-PHY with shared banks between
+ multi-ports is first version, otherwise is second veriosn,
+ so you can easily distinguish them by banks layout.
+
+config PHY_MTK_XSPHY
+ tristate "MediaTek XS-PHY Driver"
+ depends on ARCH_MEDIATEK && OF
+ select GENERIC_PHY
+ help
+ Enable this to support the SuperSpeedPlus XS-PHY transceiver for
+ USB3.1 GEN2 controllers on MediaTek chips. The driver supports
+ multiple USB2.0, USB3.1 GEN2 ports.
diff --git a/drivers/phy/mediatek/Makefile b/drivers/phy/mediatek/Makefile
new file mode 100644
index 000000000..ee49edc97
--- /dev/null
+++ b/drivers/phy/mediatek/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the phy drivers.
+#
+
+obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o
+obj-$(CONFIG_PHY_MTK_XSPHY) += phy-mtk-xsphy.o
diff --git a/drivers/phy/mediatek/phy-mtk-tphy.c b/drivers/phy/mediatek/phy-mtk-tphy.c
new file mode 100644
index 000000000..3eb8e1bd7
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-tphy.c
@@ -0,0 +1,1192 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ * Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
+ *
+ */
+
+#include <dt-bindings/phy/phy.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+/* version V1 sub-banks offset base address */
+/* banks shared by multiple phys */
+#define SSUSB_SIFSLV_V1_SPLLC 0x000 /* shared by u3 phys */
+#define SSUSB_SIFSLV_V1_U2FREQ 0x100 /* shared by u2 phys */
+#define SSUSB_SIFSLV_V1_CHIP 0x300 /* shared by u3 phys */
+/* u2 phy bank */
+#define SSUSB_SIFSLV_V1_U2PHY_COM 0x000
+/* u3/pcie/sata phy banks */
+#define SSUSB_SIFSLV_V1_U3PHYD 0x000
+#define SSUSB_SIFSLV_V1_U3PHYA 0x200
+
+/* version V2 sub-banks offset base address */
+/* u2 phy banks */
+#define SSUSB_SIFSLV_V2_MISC 0x000
+#define SSUSB_SIFSLV_V2_U2FREQ 0x100
+#define SSUSB_SIFSLV_V2_U2PHY_COM 0x300
+/* u3/pcie/sata phy banks */
+#define SSUSB_SIFSLV_V2_SPLLC 0x000
+#define SSUSB_SIFSLV_V2_CHIP 0x100
+#define SSUSB_SIFSLV_V2_U3PHYD 0x200
+#define SSUSB_SIFSLV_V2_U3PHYA 0x400
+
+#define U3P_USBPHYACR0 0x000
+#define PA0_RG_U2PLL_FORCE_ON BIT(15)
+#define PA0_RG_USB20_INTR_EN BIT(5)
+
+#define U3P_USBPHYACR1 0x004
+#define PA1_RG_VRT_SEL GENMASK(14, 12)
+#define PA1_RG_VRT_SEL_VAL(x) ((0x7 & (x)) << 12)
+#define PA1_RG_TERM_SEL GENMASK(10, 8)
+#define PA1_RG_TERM_SEL_VAL(x) ((0x7 & (x)) << 8)
+
+#define U3P_USBPHYACR2 0x008
+#define PA2_RG_SIF_U2PLL_FORCE_EN BIT(18)
+
+#define U3P_USBPHYACR5 0x014
+#define PA5_RG_U2_HSTX_SRCAL_EN BIT(15)
+#define PA5_RG_U2_HSTX_SRCTRL GENMASK(14, 12)
+#define PA5_RG_U2_HSTX_SRCTRL_VAL(x) ((0x7 & (x)) << 12)
+#define PA5_RG_U2_HS_100U_U3_EN BIT(11)
+
+#define U3P_USBPHYACR6 0x018
+#define PA6_RG_U2_BC11_SW_EN BIT(23)
+#define PA6_RG_U2_OTG_VBUSCMP_EN BIT(20)
+#define PA6_RG_U2_SQTH GENMASK(3, 0)
+#define PA6_RG_U2_SQTH_VAL(x) (0xf & (x))
+
+#define U3P_U2PHYACR4 0x020
+#define P2C_RG_USB20_GPIO_CTL BIT(9)
+#define P2C_USB20_GPIO_MODE BIT(8)
+#define P2C_U2_GPIO_CTR_MSK (P2C_RG_USB20_GPIO_CTL | P2C_USB20_GPIO_MODE)
+
+#define U3D_U2PHYDCR0 0x060
+#define P2C_RG_SIF_U2PLL_FORCE_ON BIT(24)
+
+#define U3P_U2PHYDTM0 0x068
+#define P2C_FORCE_UART_EN BIT(26)
+#define P2C_FORCE_DATAIN BIT(23)
+#define P2C_FORCE_DM_PULLDOWN BIT(21)
+#define P2C_FORCE_DP_PULLDOWN BIT(20)
+#define P2C_FORCE_XCVRSEL BIT(19)
+#define P2C_FORCE_SUSPENDM BIT(18)
+#define P2C_FORCE_TERMSEL BIT(17)
+#define P2C_RG_DATAIN GENMASK(13, 10)
+#define P2C_RG_DATAIN_VAL(x) ((0xf & (x)) << 10)
+#define P2C_RG_DMPULLDOWN BIT(7)
+#define P2C_RG_DPPULLDOWN BIT(6)
+#define P2C_RG_XCVRSEL GENMASK(5, 4)
+#define P2C_RG_XCVRSEL_VAL(x) ((0x3 & (x)) << 4)
+#define P2C_RG_SUSPENDM BIT(3)
+#define P2C_RG_TERMSEL BIT(2)
+#define P2C_DTM0_PART_MASK \
+ (P2C_FORCE_DATAIN | P2C_FORCE_DM_PULLDOWN | \
+ P2C_FORCE_DP_PULLDOWN | P2C_FORCE_XCVRSEL | \
+ P2C_FORCE_TERMSEL | P2C_RG_DMPULLDOWN | \
+ P2C_RG_DPPULLDOWN | P2C_RG_TERMSEL)
+
+#define U3P_U2PHYDTM1 0x06C
+#define P2C_RG_UART_EN BIT(16)
+#define P2C_FORCE_IDDIG BIT(9)
+#define P2C_RG_VBUSVALID BIT(5)
+#define P2C_RG_SESSEND BIT(4)
+#define P2C_RG_AVALID BIT(2)
+#define P2C_RG_IDDIG BIT(1)
+
+#define U3P_U2PHYBC12C 0x080
+#define P2C_RG_CHGDT_EN BIT(0)
+
+#define U3P_U3_CHIP_GPIO_CTLD 0x0c
+#define P3C_REG_IP_SW_RST BIT(31)
+#define P3C_MCU_BUS_CK_GATE_EN BIT(30)
+#define P3C_FORCE_IP_SW_RST BIT(29)
+
+#define U3P_U3_CHIP_GPIO_CTLE 0x10
+#define P3C_RG_SWRST_U3_PHYD BIT(25)
+#define P3C_RG_SWRST_U3_PHYD_FORCE_EN BIT(24)
+
+#define U3P_U3_PHYA_REG0 0x000
+#define P3A_RG_CLKDRV_OFF GENMASK(3, 2)
+#define P3A_RG_CLKDRV_OFF_VAL(x) ((0x3 & (x)) << 2)
+
+#define U3P_U3_PHYA_REG1 0x004
+#define P3A_RG_CLKDRV_AMP GENMASK(31, 29)
+#define P3A_RG_CLKDRV_AMP_VAL(x) ((0x7 & (x)) << 29)
+
+#define U3P_U3_PHYA_REG6 0x018
+#define P3A_RG_TX_EIDLE_CM GENMASK(31, 28)
+#define P3A_RG_TX_EIDLE_CM_VAL(x) ((0xf & (x)) << 28)
+
+#define U3P_U3_PHYA_REG9 0x024
+#define P3A_RG_RX_DAC_MUX GENMASK(5, 1)
+#define P3A_RG_RX_DAC_MUX_VAL(x) ((0x1f & (x)) << 1)
+
+#define U3P_U3_PHYA_DA_REG0 0x100
+#define P3A_RG_XTAL_EXT_PE2H GENMASK(17, 16)
+#define P3A_RG_XTAL_EXT_PE2H_VAL(x) ((0x3 & (x)) << 16)
+#define P3A_RG_XTAL_EXT_PE1H GENMASK(13, 12)
+#define P3A_RG_XTAL_EXT_PE1H_VAL(x) ((0x3 & (x)) << 12)
+#define P3A_RG_XTAL_EXT_EN_U3 GENMASK(11, 10)
+#define P3A_RG_XTAL_EXT_EN_U3_VAL(x) ((0x3 & (x)) << 10)
+
+#define U3P_U3_PHYA_DA_REG4 0x108
+#define P3A_RG_PLL_DIVEN_PE2H GENMASK(21, 19)
+#define P3A_RG_PLL_BC_PE2H GENMASK(7, 6)
+#define P3A_RG_PLL_BC_PE2H_VAL(x) ((0x3 & (x)) << 6)
+
+#define U3P_U3_PHYA_DA_REG5 0x10c
+#define P3A_RG_PLL_BR_PE2H GENMASK(29, 28)
+#define P3A_RG_PLL_BR_PE2H_VAL(x) ((0x3 & (x)) << 28)
+#define P3A_RG_PLL_IC_PE2H GENMASK(15, 12)
+#define P3A_RG_PLL_IC_PE2H_VAL(x) ((0xf & (x)) << 12)
+
+#define U3P_U3_PHYA_DA_REG6 0x110
+#define P3A_RG_PLL_IR_PE2H GENMASK(19, 16)
+#define P3A_RG_PLL_IR_PE2H_VAL(x) ((0xf & (x)) << 16)
+
+#define U3P_U3_PHYA_DA_REG7 0x114
+#define P3A_RG_PLL_BP_PE2H GENMASK(19, 16)
+#define P3A_RG_PLL_BP_PE2H_VAL(x) ((0xf & (x)) << 16)
+
+#define U3P_U3_PHYA_DA_REG20 0x13c
+#define P3A_RG_PLL_DELTA1_PE2H GENMASK(31, 16)
+#define P3A_RG_PLL_DELTA1_PE2H_VAL(x) ((0xffff & (x)) << 16)
+
+#define U3P_U3_PHYA_DA_REG25 0x148
+#define P3A_RG_PLL_DELTA_PE2H GENMASK(15, 0)
+#define P3A_RG_PLL_DELTA_PE2H_VAL(x) (0xffff & (x))
+
+#define U3P_U3_PHYD_LFPS1 0x00c
+#define P3D_RG_FWAKE_TH GENMASK(21, 16)
+#define P3D_RG_FWAKE_TH_VAL(x) ((0x3f & (x)) << 16)
+
+#define U3P_U3_PHYD_CDR1 0x05c
+#define P3D_RG_CDR_BIR_LTD1 GENMASK(28, 24)
+#define P3D_RG_CDR_BIR_LTD1_VAL(x) ((0x1f & (x)) << 24)
+#define P3D_RG_CDR_BIR_LTD0 GENMASK(12, 8)
+#define P3D_RG_CDR_BIR_LTD0_VAL(x) ((0x1f & (x)) << 8)
+
+#define U3P_U3_PHYD_RXDET1 0x128
+#define P3D_RG_RXDET_STB2_SET GENMASK(17, 9)
+#define P3D_RG_RXDET_STB2_SET_VAL(x) ((0x1ff & (x)) << 9)
+
+#define U3P_U3_PHYD_RXDET2 0x12c
+#define P3D_RG_RXDET_STB2_SET_P3 GENMASK(8, 0)
+#define P3D_RG_RXDET_STB2_SET_P3_VAL(x) (0x1ff & (x))
+
+#define U3P_SPLLC_XTALCTL3 0x018
+#define XC3_RG_U3_XTAL_RX_PWD BIT(9)
+#define XC3_RG_U3_FRC_XTAL_RX_PWD BIT(8)
+
+#define U3P_U2FREQ_FMCR0 0x00
+#define P2F_RG_MONCLK_SEL GENMASK(27, 26)
+#define P2F_RG_MONCLK_SEL_VAL(x) ((0x3 & (x)) << 26)
+#define P2F_RG_FREQDET_EN BIT(24)
+#define P2F_RG_CYCLECNT GENMASK(23, 0)
+#define P2F_RG_CYCLECNT_VAL(x) ((P2F_RG_CYCLECNT) & (x))
+
+#define U3P_U2FREQ_VALUE 0x0c
+
+#define U3P_U2FREQ_FMMONR1 0x10
+#define P2F_USB_FM_VALID BIT(0)
+#define P2F_RG_FRCK_EN BIT(8)
+
+#define U3P_REF_CLK 26 /* MHZ */
+#define U3P_SLEW_RATE_COEF 28
+#define U3P_SR_COEF_DIVISOR 1000
+#define U3P_FM_DET_CYCLE_CNT 1024
+
+/* SATA register setting */
+#define PHYD_CTRL_SIGNAL_MODE4 0x1c
+/* CDR Charge Pump P-path current adjustment */
+#define RG_CDR_BICLTD1_GEN1_MSK GENMASK(23, 20)
+#define RG_CDR_BICLTD1_GEN1_VAL(x) ((0xf & (x)) << 20)
+#define RG_CDR_BICLTD0_GEN1_MSK GENMASK(11, 8)
+#define RG_CDR_BICLTD0_GEN1_VAL(x) ((0xf & (x)) << 8)
+
+#define PHYD_DESIGN_OPTION2 0x24
+/* Symbol lock count selection */
+#define RG_LOCK_CNT_SEL_MSK GENMASK(5, 4)
+#define RG_LOCK_CNT_SEL_VAL(x) ((0x3 & (x)) << 4)
+
+#define PHYD_DESIGN_OPTION9 0x40
+/* COMWAK GAP width window */
+#define RG_TG_MAX_MSK GENMASK(20, 16)
+#define RG_TG_MAX_VAL(x) ((0x1f & (x)) << 16)
+/* COMINIT GAP width window */
+#define RG_T2_MAX_MSK GENMASK(13, 8)
+#define RG_T2_MAX_VAL(x) ((0x3f & (x)) << 8)
+/* COMWAK GAP width window */
+#define RG_TG_MIN_MSK GENMASK(7, 5)
+#define RG_TG_MIN_VAL(x) ((0x7 & (x)) << 5)
+/* COMINIT GAP width window */
+#define RG_T2_MIN_MSK GENMASK(4, 0)
+#define RG_T2_MIN_VAL(x) (0x1f & (x))
+
+#define ANA_RG_CTRL_SIGNAL1 0x4c
+/* TX driver tail current control for 0dB de-empahsis mdoe for Gen1 speed */
+#define RG_IDRV_0DB_GEN1_MSK GENMASK(13, 8)
+#define RG_IDRV_0DB_GEN1_VAL(x) ((0x3f & (x)) << 8)
+
+#define ANA_RG_CTRL_SIGNAL4 0x58
+#define RG_CDR_BICLTR_GEN1_MSK GENMASK(23, 20)
+#define RG_CDR_BICLTR_GEN1_VAL(x) ((0xf & (x)) << 20)
+/* Loop filter R1 resistance adjustment for Gen1 speed */
+#define RG_CDR_BR_GEN2_MSK GENMASK(10, 8)
+#define RG_CDR_BR_GEN2_VAL(x) ((0x7 & (x)) << 8)
+
+#define ANA_RG_CTRL_SIGNAL6 0x60
+/* I-path capacitance adjustment for Gen1 */
+#define RG_CDR_BC_GEN1_MSK GENMASK(28, 24)
+#define RG_CDR_BC_GEN1_VAL(x) ((0x1f & (x)) << 24)
+#define RG_CDR_BIRLTR_GEN1_MSK GENMASK(4, 0)
+#define RG_CDR_BIRLTR_GEN1_VAL(x) (0x1f & (x))
+
+#define ANA_EQ_EYE_CTRL_SIGNAL1 0x6c
+/* RX Gen1 LEQ tuning step */
+#define RG_EQ_DLEQ_LFI_GEN1_MSK GENMASK(11, 8)
+#define RG_EQ_DLEQ_LFI_GEN1_VAL(x) ((0xf & (x)) << 8)
+
+#define ANA_EQ_EYE_CTRL_SIGNAL4 0xd8
+#define RG_CDR_BIRLTD0_GEN1_MSK GENMASK(20, 16)
+#define RG_CDR_BIRLTD0_GEN1_VAL(x) ((0x1f & (x)) << 16)
+
+#define ANA_EQ_EYE_CTRL_SIGNAL5 0xdc
+#define RG_CDR_BIRLTD0_GEN3_MSK GENMASK(4, 0)
+#define RG_CDR_BIRLTD0_GEN3_VAL(x) (0x1f & (x))
+
+enum mtk_phy_version {
+ MTK_PHY_V1 = 1,
+ MTK_PHY_V2,
+};
+
+struct mtk_phy_pdata {
+ /* avoid RX sensitivity level degradation only for mt8173 */
+ bool avoid_rx_sen_degradation;
+ enum mtk_phy_version version;
+};
+
+struct u2phy_banks {
+ void __iomem *misc;
+ void __iomem *fmreg;
+ void __iomem *com;
+};
+
+struct u3phy_banks {
+ void __iomem *spllc;
+ void __iomem *chip;
+ void __iomem *phyd; /* include u3phyd_bank2 */
+ void __iomem *phya; /* include u3phya_da */
+};
+
+struct mtk_phy_instance {
+ struct phy *phy;
+ void __iomem *port_base;
+ union {
+ struct u2phy_banks u2_banks;
+ struct u3phy_banks u3_banks;
+ };
+ struct clk *ref_clk; /* reference clock of anolog phy */
+ u32 index;
+ u8 type;
+ int eye_src;
+ int eye_vrt;
+ int eye_term;
+ bool bc12_en;
+};
+
+struct mtk_tphy {
+ struct device *dev;
+ void __iomem *sif_base; /* only shared sif */
+ /* deprecated, use @ref_clk instead in phy instance */
+ struct clk *u3phya_ref; /* reference clock of usb3 anolog phy */
+ const struct mtk_phy_pdata *pdata;
+ struct mtk_phy_instance **phys;
+ int nphys;
+ int src_ref_clk; /* MHZ, reference clock for slew rate calibrate */
+ int src_coef; /* coefficient for slew rate calibrate */
+};
+
+static void hs_slew_rate_calibrate(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ void __iomem *fmreg = u2_banks->fmreg;
+ void __iomem *com = u2_banks->com;
+ int calibration_val;
+ int fm_out;
+ u32 tmp;
+
+ /* use force value */
+ if (instance->eye_src)
+ return;
+
+ /* enable USB ring oscillator */
+ tmp = readl(com + U3P_USBPHYACR5);
+ tmp |= PA5_RG_U2_HSTX_SRCAL_EN;
+ writel(tmp, com + U3P_USBPHYACR5);
+ udelay(1);
+
+ /*enable free run clock */
+ tmp = readl(fmreg + U3P_U2FREQ_FMMONR1);
+ tmp |= P2F_RG_FRCK_EN;
+ writel(tmp, fmreg + U3P_U2FREQ_FMMONR1);
+
+ /* set cycle count as 1024, and select u2 channel */
+ tmp = readl(fmreg + U3P_U2FREQ_FMCR0);
+ tmp &= ~(P2F_RG_CYCLECNT | P2F_RG_MONCLK_SEL);
+ tmp |= P2F_RG_CYCLECNT_VAL(U3P_FM_DET_CYCLE_CNT);
+ if (tphy->pdata->version == MTK_PHY_V1)
+ tmp |= P2F_RG_MONCLK_SEL_VAL(instance->index >> 1);
+
+ writel(tmp, fmreg + U3P_U2FREQ_FMCR0);
+
+ /* enable frequency meter */
+ tmp = readl(fmreg + U3P_U2FREQ_FMCR0);
+ tmp |= P2F_RG_FREQDET_EN;
+ writel(tmp, fmreg + U3P_U2FREQ_FMCR0);
+
+ /* ignore return value */
+ readl_poll_timeout(fmreg + U3P_U2FREQ_FMMONR1, tmp,
+ (tmp & P2F_USB_FM_VALID), 10, 200);
+
+ fm_out = readl(fmreg + U3P_U2FREQ_VALUE);
+
+ /* disable frequency meter */
+ tmp = readl(fmreg + U3P_U2FREQ_FMCR0);
+ tmp &= ~P2F_RG_FREQDET_EN;
+ writel(tmp, fmreg + U3P_U2FREQ_FMCR0);
+
+ /*disable free run clock */
+ tmp = readl(fmreg + U3P_U2FREQ_FMMONR1);
+ tmp &= ~P2F_RG_FRCK_EN;
+ writel(tmp, fmreg + U3P_U2FREQ_FMMONR1);
+
+ if (fm_out) {
+ /* ( 1024 / FM_OUT ) x reference clock frequency x coef */
+ tmp = tphy->src_ref_clk * tphy->src_coef;
+ tmp = (tmp * U3P_FM_DET_CYCLE_CNT) / fm_out;
+ calibration_val = DIV_ROUND_CLOSEST(tmp, U3P_SR_COEF_DIVISOR);
+ } else {
+ /* if FM detection fail, set default value */
+ calibration_val = 4;
+ }
+ dev_dbg(tphy->dev, "phy:%d, fm_out:%d, calib:%d (clk:%d, coef:%d)\n",
+ instance->index, fm_out, calibration_val,
+ tphy->src_ref_clk, tphy->src_coef);
+
+ /* set HS slew rate */
+ tmp = readl(com + U3P_USBPHYACR5);
+ tmp &= ~PA5_RG_U2_HSTX_SRCTRL;
+ tmp |= PA5_RG_U2_HSTX_SRCTRL_VAL(calibration_val);
+ writel(tmp, com + U3P_USBPHYACR5);
+
+ /* disable USB ring oscillator */
+ tmp = readl(com + U3P_USBPHYACR5);
+ tmp &= ~PA5_RG_U2_HSTX_SRCAL_EN;
+ writel(tmp, com + U3P_USBPHYACR5);
+}
+
+static void u3_phy_instance_init(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u3phy_banks *u3_banks = &instance->u3_banks;
+ u32 tmp;
+
+ /* gating PCIe Analog XTAL clock */
+ tmp = readl(u3_banks->spllc + U3P_SPLLC_XTALCTL3);
+ tmp |= XC3_RG_U3_XTAL_RX_PWD | XC3_RG_U3_FRC_XTAL_RX_PWD;
+ writel(tmp, u3_banks->spllc + U3P_SPLLC_XTALCTL3);
+
+ /* gating XSQ */
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_DA_REG0);
+ tmp &= ~P3A_RG_XTAL_EXT_EN_U3;
+ tmp |= P3A_RG_XTAL_EXT_EN_U3_VAL(2);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_DA_REG0);
+
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_REG9);
+ tmp &= ~P3A_RG_RX_DAC_MUX;
+ tmp |= P3A_RG_RX_DAC_MUX_VAL(4);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_REG9);
+
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_REG6);
+ tmp &= ~P3A_RG_TX_EIDLE_CM;
+ tmp |= P3A_RG_TX_EIDLE_CM_VAL(0xe);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_REG6);
+
+ tmp = readl(u3_banks->phyd + U3P_U3_PHYD_CDR1);
+ tmp &= ~(P3D_RG_CDR_BIR_LTD0 | P3D_RG_CDR_BIR_LTD1);
+ tmp |= P3D_RG_CDR_BIR_LTD0_VAL(0xc) | P3D_RG_CDR_BIR_LTD1_VAL(0x3);
+ writel(tmp, u3_banks->phyd + U3P_U3_PHYD_CDR1);
+
+ tmp = readl(u3_banks->phyd + U3P_U3_PHYD_LFPS1);
+ tmp &= ~P3D_RG_FWAKE_TH;
+ tmp |= P3D_RG_FWAKE_TH_VAL(0x34);
+ writel(tmp, u3_banks->phyd + U3P_U3_PHYD_LFPS1);
+
+ tmp = readl(u3_banks->phyd + U3P_U3_PHYD_RXDET1);
+ tmp &= ~P3D_RG_RXDET_STB2_SET;
+ tmp |= P3D_RG_RXDET_STB2_SET_VAL(0x10);
+ writel(tmp, u3_banks->phyd + U3P_U3_PHYD_RXDET1);
+
+ tmp = readl(u3_banks->phyd + U3P_U3_PHYD_RXDET2);
+ tmp &= ~P3D_RG_RXDET_STB2_SET_P3;
+ tmp |= P3D_RG_RXDET_STB2_SET_P3_VAL(0x10);
+ writel(tmp, u3_banks->phyd + U3P_U3_PHYD_RXDET2);
+
+ dev_dbg(tphy->dev, "%s(%d)\n", __func__, instance->index);
+}
+
+static void u2_phy_instance_init(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ void __iomem *com = u2_banks->com;
+ u32 index = instance->index;
+ u32 tmp;
+
+ /* switch to USB function, and enable usb pll */
+ tmp = readl(com + U3P_U2PHYDTM0);
+ tmp &= ~(P2C_FORCE_UART_EN | P2C_FORCE_SUSPENDM);
+ tmp |= P2C_RG_XCVRSEL_VAL(1) | P2C_RG_DATAIN_VAL(0);
+ writel(tmp, com + U3P_U2PHYDTM0);
+
+ tmp = readl(com + U3P_U2PHYDTM1);
+ tmp &= ~P2C_RG_UART_EN;
+ writel(tmp, com + U3P_U2PHYDTM1);
+
+ tmp = readl(com + U3P_USBPHYACR0);
+ tmp |= PA0_RG_USB20_INTR_EN;
+ writel(tmp, com + U3P_USBPHYACR0);
+
+ /* disable switch 100uA current to SSUSB */
+ tmp = readl(com + U3P_USBPHYACR5);
+ tmp &= ~PA5_RG_U2_HS_100U_U3_EN;
+ writel(tmp, com + U3P_USBPHYACR5);
+
+ if (!index) {
+ tmp = readl(com + U3P_U2PHYACR4);
+ tmp &= ~P2C_U2_GPIO_CTR_MSK;
+ writel(tmp, com + U3P_U2PHYACR4);
+ }
+
+ if (tphy->pdata->avoid_rx_sen_degradation) {
+ if (!index) {
+ tmp = readl(com + U3P_USBPHYACR2);
+ tmp |= PA2_RG_SIF_U2PLL_FORCE_EN;
+ writel(tmp, com + U3P_USBPHYACR2);
+
+ tmp = readl(com + U3D_U2PHYDCR0);
+ tmp &= ~P2C_RG_SIF_U2PLL_FORCE_ON;
+ writel(tmp, com + U3D_U2PHYDCR0);
+ } else {
+ tmp = readl(com + U3D_U2PHYDCR0);
+ tmp |= P2C_RG_SIF_U2PLL_FORCE_ON;
+ writel(tmp, com + U3D_U2PHYDCR0);
+
+ tmp = readl(com + U3P_U2PHYDTM0);
+ tmp |= P2C_RG_SUSPENDM | P2C_FORCE_SUSPENDM;
+ writel(tmp, com + U3P_U2PHYDTM0);
+ }
+ }
+
+ tmp = readl(com + U3P_USBPHYACR6);
+ tmp &= ~PA6_RG_U2_BC11_SW_EN; /* DP/DM BC1.1 path Disable */
+ tmp &= ~PA6_RG_U2_SQTH;
+ tmp |= PA6_RG_U2_SQTH_VAL(2);
+ writel(tmp, com + U3P_USBPHYACR6);
+
+ dev_dbg(tphy->dev, "%s(%d)\n", __func__, index);
+}
+
+static void u2_phy_instance_power_on(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ void __iomem *com = u2_banks->com;
+ u32 index = instance->index;
+ u32 tmp;
+
+ tmp = readl(com + U3P_U2PHYDTM0);
+ tmp &= ~(P2C_RG_XCVRSEL | P2C_RG_DATAIN | P2C_DTM0_PART_MASK);
+ writel(tmp, com + U3P_U2PHYDTM0);
+
+ /* OTG Enable */
+ tmp = readl(com + U3P_USBPHYACR6);
+ tmp |= PA6_RG_U2_OTG_VBUSCMP_EN;
+ writel(tmp, com + U3P_USBPHYACR6);
+
+ tmp = readl(com + U3P_U2PHYDTM1);
+ tmp |= P2C_RG_VBUSVALID | P2C_RG_AVALID;
+ tmp &= ~P2C_RG_SESSEND;
+ writel(tmp, com + U3P_U2PHYDTM1);
+
+ if (tphy->pdata->avoid_rx_sen_degradation && index) {
+ tmp = readl(com + U3D_U2PHYDCR0);
+ tmp |= P2C_RG_SIF_U2PLL_FORCE_ON;
+ writel(tmp, com + U3D_U2PHYDCR0);
+
+ tmp = readl(com + U3P_U2PHYDTM0);
+ tmp |= P2C_RG_SUSPENDM | P2C_FORCE_SUSPENDM;
+ writel(tmp, com + U3P_U2PHYDTM0);
+ }
+ dev_dbg(tphy->dev, "%s(%d)\n", __func__, index);
+}
+
+static void u2_phy_instance_power_off(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ void __iomem *com = u2_banks->com;
+ u32 index = instance->index;
+ u32 tmp;
+
+ tmp = readl(com + U3P_U2PHYDTM0);
+ tmp &= ~(P2C_RG_XCVRSEL | P2C_RG_DATAIN);
+ writel(tmp, com + U3P_U2PHYDTM0);
+
+ /* OTG Disable */
+ tmp = readl(com + U3P_USBPHYACR6);
+ tmp &= ~PA6_RG_U2_OTG_VBUSCMP_EN;
+ writel(tmp, com + U3P_USBPHYACR6);
+
+ tmp = readl(com + U3P_U2PHYDTM1);
+ tmp &= ~(P2C_RG_VBUSVALID | P2C_RG_AVALID);
+ tmp |= P2C_RG_SESSEND;
+ writel(tmp, com + U3P_U2PHYDTM1);
+
+ if (tphy->pdata->avoid_rx_sen_degradation && index) {
+ tmp = readl(com + U3P_U2PHYDTM0);
+ tmp &= ~(P2C_RG_SUSPENDM | P2C_FORCE_SUSPENDM);
+ writel(tmp, com + U3P_U2PHYDTM0);
+
+ tmp = readl(com + U3D_U2PHYDCR0);
+ tmp &= ~P2C_RG_SIF_U2PLL_FORCE_ON;
+ writel(tmp, com + U3D_U2PHYDCR0);
+ }
+
+ dev_dbg(tphy->dev, "%s(%d)\n", __func__, index);
+}
+
+static void u2_phy_instance_exit(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ void __iomem *com = u2_banks->com;
+ u32 index = instance->index;
+ u32 tmp;
+
+ if (tphy->pdata->avoid_rx_sen_degradation && index) {
+ tmp = readl(com + U3D_U2PHYDCR0);
+ tmp &= ~P2C_RG_SIF_U2PLL_FORCE_ON;
+ writel(tmp, com + U3D_U2PHYDCR0);
+
+ tmp = readl(com + U3P_U2PHYDTM0);
+ tmp &= ~P2C_FORCE_SUSPENDM;
+ writel(tmp, com + U3P_U2PHYDTM0);
+ }
+}
+
+static void u2_phy_instance_set_mode(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance,
+ enum phy_mode mode)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ u32 tmp;
+
+ tmp = readl(u2_banks->com + U3P_U2PHYDTM1);
+ switch (mode) {
+ case PHY_MODE_USB_DEVICE:
+ tmp |= P2C_FORCE_IDDIG | P2C_RG_IDDIG;
+ break;
+ case PHY_MODE_USB_HOST:
+ tmp |= P2C_FORCE_IDDIG;
+ tmp &= ~P2C_RG_IDDIG;
+ break;
+ case PHY_MODE_USB_OTG:
+ tmp &= ~(P2C_FORCE_IDDIG | P2C_RG_IDDIG);
+ break;
+ default:
+ return;
+ }
+ writel(tmp, u2_banks->com + U3P_U2PHYDTM1);
+}
+
+static void pcie_phy_instance_init(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u3phy_banks *u3_banks = &instance->u3_banks;
+ u32 tmp;
+
+ if (tphy->pdata->version != MTK_PHY_V1)
+ return;
+
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_DA_REG0);
+ tmp &= ~(P3A_RG_XTAL_EXT_PE1H | P3A_RG_XTAL_EXT_PE2H);
+ tmp |= P3A_RG_XTAL_EXT_PE1H_VAL(0x2) | P3A_RG_XTAL_EXT_PE2H_VAL(0x2);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_DA_REG0);
+
+ /* ref clk drive */
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_REG1);
+ tmp &= ~P3A_RG_CLKDRV_AMP;
+ tmp |= P3A_RG_CLKDRV_AMP_VAL(0x4);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_REG1);
+
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_REG0);
+ tmp &= ~P3A_RG_CLKDRV_OFF;
+ tmp |= P3A_RG_CLKDRV_OFF_VAL(0x1);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_REG0);
+
+ /* SSC delta -5000ppm */
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_DA_REG20);
+ tmp &= ~P3A_RG_PLL_DELTA1_PE2H;
+ tmp |= P3A_RG_PLL_DELTA1_PE2H_VAL(0x3c);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_DA_REG20);
+
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_DA_REG25);
+ tmp &= ~P3A_RG_PLL_DELTA_PE2H;
+ tmp |= P3A_RG_PLL_DELTA_PE2H_VAL(0x36);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_DA_REG25);
+
+ /* change pll BW 0.6M */
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_DA_REG5);
+ tmp &= ~(P3A_RG_PLL_BR_PE2H | P3A_RG_PLL_IC_PE2H);
+ tmp |= P3A_RG_PLL_BR_PE2H_VAL(0x1) | P3A_RG_PLL_IC_PE2H_VAL(0x1);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_DA_REG5);
+
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_DA_REG4);
+ tmp &= ~(P3A_RG_PLL_DIVEN_PE2H | P3A_RG_PLL_BC_PE2H);
+ tmp |= P3A_RG_PLL_BC_PE2H_VAL(0x3);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_DA_REG4);
+
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_DA_REG6);
+ tmp &= ~P3A_RG_PLL_IR_PE2H;
+ tmp |= P3A_RG_PLL_IR_PE2H_VAL(0x2);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_DA_REG6);
+
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_DA_REG7);
+ tmp &= ~P3A_RG_PLL_BP_PE2H;
+ tmp |= P3A_RG_PLL_BP_PE2H_VAL(0xa);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_DA_REG7);
+
+ /* Tx Detect Rx Timing: 10us -> 5us */
+ tmp = readl(u3_banks->phyd + U3P_U3_PHYD_RXDET1);
+ tmp &= ~P3D_RG_RXDET_STB2_SET;
+ tmp |= P3D_RG_RXDET_STB2_SET_VAL(0x10);
+ writel(tmp, u3_banks->phyd + U3P_U3_PHYD_RXDET1);
+
+ tmp = readl(u3_banks->phyd + U3P_U3_PHYD_RXDET2);
+ tmp &= ~P3D_RG_RXDET_STB2_SET_P3;
+ tmp |= P3D_RG_RXDET_STB2_SET_P3_VAL(0x10);
+ writel(tmp, u3_banks->phyd + U3P_U3_PHYD_RXDET2);
+
+ /* wait for PCIe subsys register to active */
+ usleep_range(2500, 3000);
+ dev_dbg(tphy->dev, "%s(%d)\n", __func__, instance->index);
+}
+
+static void pcie_phy_instance_power_on(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u3phy_banks *bank = &instance->u3_banks;
+ u32 tmp;
+
+ tmp = readl(bank->chip + U3P_U3_CHIP_GPIO_CTLD);
+ tmp &= ~(P3C_FORCE_IP_SW_RST | P3C_REG_IP_SW_RST);
+ writel(tmp, bank->chip + U3P_U3_CHIP_GPIO_CTLD);
+
+ tmp = readl(bank->chip + U3P_U3_CHIP_GPIO_CTLE);
+ tmp &= ~(P3C_RG_SWRST_U3_PHYD_FORCE_EN | P3C_RG_SWRST_U3_PHYD);
+ writel(tmp, bank->chip + U3P_U3_CHIP_GPIO_CTLE);
+}
+
+static void pcie_phy_instance_power_off(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+
+{
+ struct u3phy_banks *bank = &instance->u3_banks;
+ u32 tmp;
+
+ tmp = readl(bank->chip + U3P_U3_CHIP_GPIO_CTLD);
+ tmp |= P3C_FORCE_IP_SW_RST | P3C_REG_IP_SW_RST;
+ writel(tmp, bank->chip + U3P_U3_CHIP_GPIO_CTLD);
+
+ tmp = readl(bank->chip + U3P_U3_CHIP_GPIO_CTLE);
+ tmp |= P3C_RG_SWRST_U3_PHYD_FORCE_EN | P3C_RG_SWRST_U3_PHYD;
+ writel(tmp, bank->chip + U3P_U3_CHIP_GPIO_CTLE);
+}
+
+static void sata_phy_instance_init(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u3phy_banks *u3_banks = &instance->u3_banks;
+ void __iomem *phyd = u3_banks->phyd;
+ u32 tmp;
+
+ /* charge current adjustment */
+ tmp = readl(phyd + ANA_RG_CTRL_SIGNAL6);
+ tmp &= ~(RG_CDR_BIRLTR_GEN1_MSK | RG_CDR_BC_GEN1_MSK);
+ tmp |= RG_CDR_BIRLTR_GEN1_VAL(0x6) | RG_CDR_BC_GEN1_VAL(0x1a);
+ writel(tmp, phyd + ANA_RG_CTRL_SIGNAL6);
+
+ tmp = readl(phyd + ANA_EQ_EYE_CTRL_SIGNAL4);
+ tmp &= ~RG_CDR_BIRLTD0_GEN1_MSK;
+ tmp |= RG_CDR_BIRLTD0_GEN1_VAL(0x18);
+ writel(tmp, phyd + ANA_EQ_EYE_CTRL_SIGNAL4);
+
+ tmp = readl(phyd + ANA_EQ_EYE_CTRL_SIGNAL5);
+ tmp &= ~RG_CDR_BIRLTD0_GEN3_MSK;
+ tmp |= RG_CDR_BIRLTD0_GEN3_VAL(0x06);
+ writel(tmp, phyd + ANA_EQ_EYE_CTRL_SIGNAL5);
+
+ tmp = readl(phyd + ANA_RG_CTRL_SIGNAL4);
+ tmp &= ~(RG_CDR_BICLTR_GEN1_MSK | RG_CDR_BR_GEN2_MSK);
+ tmp |= RG_CDR_BICLTR_GEN1_VAL(0x0c) | RG_CDR_BR_GEN2_VAL(0x07);
+ writel(tmp, phyd + ANA_RG_CTRL_SIGNAL4);
+
+ tmp = readl(phyd + PHYD_CTRL_SIGNAL_MODE4);
+ tmp &= ~(RG_CDR_BICLTD0_GEN1_MSK | RG_CDR_BICLTD1_GEN1_MSK);
+ tmp |= RG_CDR_BICLTD0_GEN1_VAL(0x08) | RG_CDR_BICLTD1_GEN1_VAL(0x02);
+ writel(tmp, phyd + PHYD_CTRL_SIGNAL_MODE4);
+
+ tmp = readl(phyd + PHYD_DESIGN_OPTION2);
+ tmp &= ~RG_LOCK_CNT_SEL_MSK;
+ tmp |= RG_LOCK_CNT_SEL_VAL(0x02);
+ writel(tmp, phyd + PHYD_DESIGN_OPTION2);
+
+ tmp = readl(phyd + PHYD_DESIGN_OPTION9);
+ tmp &= ~(RG_T2_MIN_MSK | RG_TG_MIN_MSK |
+ RG_T2_MAX_MSK | RG_TG_MAX_MSK);
+ tmp |= RG_T2_MIN_VAL(0x12) | RG_TG_MIN_VAL(0x04) |
+ RG_T2_MAX_VAL(0x31) | RG_TG_MAX_VAL(0x0e);
+ writel(tmp, phyd + PHYD_DESIGN_OPTION9);
+
+ tmp = readl(phyd + ANA_RG_CTRL_SIGNAL1);
+ tmp &= ~RG_IDRV_0DB_GEN1_MSK;
+ tmp |= RG_IDRV_0DB_GEN1_VAL(0x20);
+ writel(tmp, phyd + ANA_RG_CTRL_SIGNAL1);
+
+ tmp = readl(phyd + ANA_EQ_EYE_CTRL_SIGNAL1);
+ tmp &= ~RG_EQ_DLEQ_LFI_GEN1_MSK;
+ tmp |= RG_EQ_DLEQ_LFI_GEN1_VAL(0x03);
+ writel(tmp, phyd + ANA_EQ_EYE_CTRL_SIGNAL1);
+
+ dev_dbg(tphy->dev, "%s(%d)\n", __func__, instance->index);
+}
+
+static void phy_v1_banks_init(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ struct u3phy_banks *u3_banks = &instance->u3_banks;
+
+ switch (instance->type) {
+ case PHY_TYPE_USB2:
+ u2_banks->misc = NULL;
+ u2_banks->fmreg = tphy->sif_base + SSUSB_SIFSLV_V1_U2FREQ;
+ u2_banks->com = instance->port_base + SSUSB_SIFSLV_V1_U2PHY_COM;
+ break;
+ case PHY_TYPE_USB3:
+ case PHY_TYPE_PCIE:
+ u3_banks->spllc = tphy->sif_base + SSUSB_SIFSLV_V1_SPLLC;
+ u3_banks->chip = tphy->sif_base + SSUSB_SIFSLV_V1_CHIP;
+ u3_banks->phyd = instance->port_base + SSUSB_SIFSLV_V1_U3PHYD;
+ u3_banks->phya = instance->port_base + SSUSB_SIFSLV_V1_U3PHYA;
+ break;
+ case PHY_TYPE_SATA:
+ u3_banks->phyd = instance->port_base + SSUSB_SIFSLV_V1_U3PHYD;
+ break;
+ default:
+ dev_err(tphy->dev, "incompatible PHY type\n");
+ return;
+ }
+}
+
+static void phy_v2_banks_init(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ struct u3phy_banks *u3_banks = &instance->u3_banks;
+
+ switch (instance->type) {
+ case PHY_TYPE_USB2:
+ u2_banks->misc = instance->port_base + SSUSB_SIFSLV_V2_MISC;
+ u2_banks->fmreg = instance->port_base + SSUSB_SIFSLV_V2_U2FREQ;
+ u2_banks->com = instance->port_base + SSUSB_SIFSLV_V2_U2PHY_COM;
+ break;
+ case PHY_TYPE_USB3:
+ case PHY_TYPE_PCIE:
+ u3_banks->spllc = instance->port_base + SSUSB_SIFSLV_V2_SPLLC;
+ u3_banks->chip = instance->port_base + SSUSB_SIFSLV_V2_CHIP;
+ u3_banks->phyd = instance->port_base + SSUSB_SIFSLV_V2_U3PHYD;
+ u3_banks->phya = instance->port_base + SSUSB_SIFSLV_V2_U3PHYA;
+ break;
+ default:
+ dev_err(tphy->dev, "incompatible PHY type\n");
+ return;
+ }
+}
+
+static void phy_parse_property(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct device *dev = &instance->phy->dev;
+
+ if (instance->type != PHY_TYPE_USB2)
+ return;
+
+ instance->bc12_en = device_property_read_bool(dev, "mediatek,bc12");
+ device_property_read_u32(dev, "mediatek,eye-src",
+ &instance->eye_src);
+ device_property_read_u32(dev, "mediatek,eye-vrt",
+ &instance->eye_vrt);
+ device_property_read_u32(dev, "mediatek,eye-term",
+ &instance->eye_term);
+ dev_dbg(dev, "bc12:%d, src:%d, vrt:%d, term:%d\n",
+ instance->bc12_en, instance->eye_src,
+ instance->eye_vrt, instance->eye_term);
+}
+
+static void u2_phy_props_set(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ void __iomem *com = u2_banks->com;
+ u32 tmp;
+
+ if (instance->bc12_en) {
+ tmp = readl(com + U3P_U2PHYBC12C);
+ tmp |= P2C_RG_CHGDT_EN; /* BC1.2 path Enable */
+ writel(tmp, com + U3P_U2PHYBC12C);
+ }
+
+ if (instance->eye_src) {
+ tmp = readl(com + U3P_USBPHYACR5);
+ tmp &= ~PA5_RG_U2_HSTX_SRCTRL;
+ tmp |= PA5_RG_U2_HSTX_SRCTRL_VAL(instance->eye_src);
+ writel(tmp, com + U3P_USBPHYACR5);
+ }
+
+ if (instance->eye_vrt) {
+ tmp = readl(com + U3P_USBPHYACR1);
+ tmp &= ~PA1_RG_VRT_SEL;
+ tmp |= PA1_RG_VRT_SEL_VAL(instance->eye_vrt);
+ writel(tmp, com + U3P_USBPHYACR1);
+ }
+
+ if (instance->eye_term) {
+ tmp = readl(com + U3P_USBPHYACR1);
+ tmp &= ~PA1_RG_TERM_SEL;
+ tmp |= PA1_RG_TERM_SEL_VAL(instance->eye_term);
+ writel(tmp, com + U3P_USBPHYACR1);
+ }
+}
+
+static int mtk_phy_init(struct phy *phy)
+{
+ struct mtk_phy_instance *instance = phy_get_drvdata(phy);
+ struct mtk_tphy *tphy = dev_get_drvdata(phy->dev.parent);
+ int ret;
+
+ ret = clk_prepare_enable(tphy->u3phya_ref);
+ if (ret) {
+ dev_err(tphy->dev, "failed to enable u3phya_ref\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(instance->ref_clk);
+ if (ret) {
+ dev_err(tphy->dev, "failed to enable ref_clk\n");
+ return ret;
+ }
+
+ switch (instance->type) {
+ case PHY_TYPE_USB2:
+ u2_phy_instance_init(tphy, instance);
+ u2_phy_props_set(tphy, instance);
+ break;
+ case PHY_TYPE_USB3:
+ u3_phy_instance_init(tphy, instance);
+ break;
+ case PHY_TYPE_PCIE:
+ pcie_phy_instance_init(tphy, instance);
+ break;
+ case PHY_TYPE_SATA:
+ sata_phy_instance_init(tphy, instance);
+ break;
+ default:
+ dev_err(tphy->dev, "incompatible PHY type\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mtk_phy_power_on(struct phy *phy)
+{
+ struct mtk_phy_instance *instance = phy_get_drvdata(phy);
+ struct mtk_tphy *tphy = dev_get_drvdata(phy->dev.parent);
+
+ if (instance->type == PHY_TYPE_USB2) {
+ u2_phy_instance_power_on(tphy, instance);
+ hs_slew_rate_calibrate(tphy, instance);
+ } else if (instance->type == PHY_TYPE_PCIE) {
+ pcie_phy_instance_power_on(tphy, instance);
+ }
+
+ return 0;
+}
+
+static int mtk_phy_power_off(struct phy *phy)
+{
+ struct mtk_phy_instance *instance = phy_get_drvdata(phy);
+ struct mtk_tphy *tphy = dev_get_drvdata(phy->dev.parent);
+
+ if (instance->type == PHY_TYPE_USB2)
+ u2_phy_instance_power_off(tphy, instance);
+ else if (instance->type == PHY_TYPE_PCIE)
+ pcie_phy_instance_power_off(tphy, instance);
+
+ return 0;
+}
+
+static int mtk_phy_exit(struct phy *phy)
+{
+ struct mtk_phy_instance *instance = phy_get_drvdata(phy);
+ struct mtk_tphy *tphy = dev_get_drvdata(phy->dev.parent);
+
+ if (instance->type == PHY_TYPE_USB2)
+ u2_phy_instance_exit(tphy, instance);
+
+ clk_disable_unprepare(instance->ref_clk);
+ clk_disable_unprepare(tphy->u3phya_ref);
+ return 0;
+}
+
+static int mtk_phy_set_mode(struct phy *phy, enum phy_mode mode)
+{
+ struct mtk_phy_instance *instance = phy_get_drvdata(phy);
+ struct mtk_tphy *tphy = dev_get_drvdata(phy->dev.parent);
+
+ if (instance->type == PHY_TYPE_USB2)
+ u2_phy_instance_set_mode(tphy, instance, mode);
+
+ return 0;
+}
+
+static struct phy *mtk_phy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct mtk_tphy *tphy = dev_get_drvdata(dev);
+ struct mtk_phy_instance *instance = NULL;
+ struct device_node *phy_np = args->np;
+ int index;
+
+ if (args->args_count != 1) {
+ dev_err(dev, "invalid number of cells in 'phy' property\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ for (index = 0; index < tphy->nphys; index++)
+ if (phy_np == tphy->phys[index]->phy->dev.of_node) {
+ instance = tphy->phys[index];
+ break;
+ }
+
+ if (!instance) {
+ dev_err(dev, "failed to find appropriate phy\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ instance->type = args->args[0];
+ if (!(instance->type == PHY_TYPE_USB2 ||
+ instance->type == PHY_TYPE_USB3 ||
+ instance->type == PHY_TYPE_PCIE ||
+ instance->type == PHY_TYPE_SATA)) {
+ dev_err(dev, "unsupported device type: %d\n", instance->type);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (tphy->pdata->version == MTK_PHY_V1) {
+ phy_v1_banks_init(tphy, instance);
+ } else if (tphy->pdata->version == MTK_PHY_V2) {
+ phy_v2_banks_init(tphy, instance);
+ } else {
+ dev_err(dev, "phy version is not supported\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ phy_parse_property(tphy, instance);
+
+ return instance->phy;
+}
+
+static const struct phy_ops mtk_tphy_ops = {
+ .init = mtk_phy_init,
+ .exit = mtk_phy_exit,
+ .power_on = mtk_phy_power_on,
+ .power_off = mtk_phy_power_off,
+ .set_mode = mtk_phy_set_mode,
+ .owner = THIS_MODULE,
+};
+
+static const struct mtk_phy_pdata tphy_v1_pdata = {
+ .avoid_rx_sen_degradation = false,
+ .version = MTK_PHY_V1,
+};
+
+static const struct mtk_phy_pdata tphy_v2_pdata = {
+ .avoid_rx_sen_degradation = false,
+ .version = MTK_PHY_V2,
+};
+
+static const struct mtk_phy_pdata mt8173_pdata = {
+ .avoid_rx_sen_degradation = true,
+ .version = MTK_PHY_V1,
+};
+
+static const struct of_device_id mtk_tphy_id_table[] = {
+ { .compatible = "mediatek,mt2701-u3phy", .data = &tphy_v1_pdata },
+ { .compatible = "mediatek,mt2712-u3phy", .data = &tphy_v2_pdata },
+ { .compatible = "mediatek,mt8173-u3phy", .data = &mt8173_pdata },
+ { .compatible = "mediatek,generic-tphy-v1", .data = &tphy_v1_pdata },
+ { .compatible = "mediatek,generic-tphy-v2", .data = &tphy_v2_pdata },
+ { },
+};
+MODULE_DEVICE_TABLE(of, mtk_tphy_id_table);
+
+static int mtk_tphy_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 resource *sif_res;
+ struct mtk_tphy *tphy;
+ struct resource res;
+ int port, retval;
+
+ tphy = devm_kzalloc(dev, sizeof(*tphy), GFP_KERNEL);
+ if (!tphy)
+ return -ENOMEM;
+
+ tphy->pdata = of_device_get_match_data(dev);
+ if (!tphy->pdata)
+ return -EINVAL;
+
+ tphy->nphys = of_get_child_count(np);
+ tphy->phys = devm_kcalloc(dev, tphy->nphys,
+ sizeof(*tphy->phys), GFP_KERNEL);
+ if (!tphy->phys)
+ return -ENOMEM;
+
+ tphy->dev = dev;
+ platform_set_drvdata(pdev, tphy);
+
+ sif_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ /* SATA phy of V1 needn't it if not shared with PCIe or USB */
+ if (sif_res && tphy->pdata->version == MTK_PHY_V1) {
+ /* get banks shared by multiple phys */
+ tphy->sif_base = devm_ioremap_resource(dev, sif_res);
+ if (IS_ERR(tphy->sif_base)) {
+ dev_err(dev, "failed to remap sif regs\n");
+ return PTR_ERR(tphy->sif_base);
+ }
+ }
+
+ /* it's deprecated, make it optional for backward compatibility */
+ tphy->u3phya_ref = devm_clk_get(dev, "u3phya_ref");
+ if (IS_ERR(tphy->u3phya_ref)) {
+ if (PTR_ERR(tphy->u3phya_ref) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ tphy->u3phya_ref = NULL;
+ }
+
+ tphy->src_ref_clk = U3P_REF_CLK;
+ tphy->src_coef = U3P_SLEW_RATE_COEF;
+ /* update parameters of slew rate calibrate if exist */
+ device_property_read_u32(dev, "mediatek,src-ref-clk-mhz",
+ &tphy->src_ref_clk);
+ device_property_read_u32(dev, "mediatek,src-coef", &tphy->src_coef);
+
+ port = 0;
+ for_each_child_of_node(np, child_np) {
+ struct mtk_phy_instance *instance;
+ struct phy *phy;
+
+ instance = devm_kzalloc(dev, sizeof(*instance), GFP_KERNEL);
+ if (!instance) {
+ retval = -ENOMEM;
+ goto put_child;
+ }
+
+ tphy->phys[port] = instance;
+
+ phy = devm_phy_create(dev, child_np, &mtk_tphy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create phy\n");
+ retval = PTR_ERR(phy);
+ goto put_child;
+ }
+
+ retval = of_address_to_resource(child_np, 0, &res);
+ if (retval) {
+ dev_err(dev, "failed to get address resource(id-%d)\n",
+ port);
+ goto put_child;
+ }
+
+ instance->port_base = devm_ioremap_resource(&phy->dev, &res);
+ if (IS_ERR(instance->port_base)) {
+ dev_err(dev, "failed to remap phy regs\n");
+ retval = PTR_ERR(instance->port_base);
+ goto put_child;
+ }
+
+ instance->phy = phy;
+ instance->index = port;
+ phy_set_drvdata(phy, instance);
+ port++;
+
+ /* if deprecated clock is provided, ignore instance's one */
+ if (tphy->u3phya_ref)
+ continue;
+
+ instance->ref_clk = devm_clk_get(&phy->dev, "ref");
+ if (IS_ERR(instance->ref_clk)) {
+ dev_err(dev, "failed to get ref_clk(id-%d)\n", port);
+ retval = PTR_ERR(instance->ref_clk);
+ goto put_child;
+ }
+ }
+
+ provider = devm_of_phy_provider_register(dev, mtk_phy_xlate);
+
+ return PTR_ERR_OR_ZERO(provider);
+put_child:
+ of_node_put(child_np);
+ return retval;
+}
+
+static struct platform_driver mtk_tphy_driver = {
+ .probe = mtk_tphy_probe,
+ .driver = {
+ .name = "mtk-tphy",
+ .of_match_table = mtk_tphy_id_table,
+ },
+};
+
+module_platform_driver(mtk_tphy_driver);
+
+MODULE_AUTHOR("Chunfeng Yun <chunfeng.yun@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek T-PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/mediatek/phy-mtk-xsphy.c b/drivers/phy/mediatek/phy-mtk-xsphy.c
new file mode 100644
index 000000000..020cd0227
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-xsphy.c
@@ -0,0 +1,600 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MediaTek USB3.1 gen2 xsphy Driver
+ *
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
+ *
+ */
+
+#include <dt-bindings/phy/phy.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+/* u2 phy banks */
+#define SSUSB_SIFSLV_MISC 0x000
+#define SSUSB_SIFSLV_U2FREQ 0x100
+#define SSUSB_SIFSLV_U2PHY_COM 0x300
+
+/* u3 phy shared banks */
+#define SSPXTP_SIFSLV_DIG_GLB 0x000
+#define SSPXTP_SIFSLV_PHYA_GLB 0x100
+
+/* u3 phy banks */
+#define SSPXTP_SIFSLV_DIG_LN_TOP 0x000
+#define SSPXTP_SIFSLV_DIG_LN_TX0 0x100
+#define SSPXTP_SIFSLV_DIG_LN_RX0 0x200
+#define SSPXTP_SIFSLV_DIG_LN_DAIF 0x300
+#define SSPXTP_SIFSLV_PHYA_LN 0x400
+
+#define XSP_U2FREQ_FMCR0 ((SSUSB_SIFSLV_U2FREQ) + 0x00)
+#define P2F_RG_FREQDET_EN BIT(24)
+#define P2F_RG_CYCLECNT GENMASK(23, 0)
+#define P2F_RG_CYCLECNT_VAL(x) ((P2F_RG_CYCLECNT) & (x))
+
+#define XSP_U2FREQ_MMONR0 ((SSUSB_SIFSLV_U2FREQ) + 0x0c)
+
+#define XSP_U2FREQ_FMMONR1 ((SSUSB_SIFSLV_U2FREQ) + 0x10)
+#define P2F_RG_FRCK_EN BIT(8)
+#define P2F_USB_FM_VALID BIT(0)
+
+#define XSP_USBPHYACR0 ((SSUSB_SIFSLV_U2PHY_COM) + 0x00)
+#define P2A0_RG_INTR_EN BIT(5)
+
+#define XSP_USBPHYACR1 ((SSUSB_SIFSLV_U2PHY_COM) + 0x04)
+#define P2A1_RG_INTR_CAL GENMASK(23, 19)
+#define P2A1_RG_INTR_CAL_VAL(x) ((0x1f & (x)) << 19)
+#define P2A1_RG_VRT_SEL GENMASK(14, 12)
+#define P2A1_RG_VRT_SEL_VAL(x) ((0x7 & (x)) << 12)
+#define P2A1_RG_TERM_SEL GENMASK(10, 8)
+#define P2A1_RG_TERM_SEL_VAL(x) ((0x7 & (x)) << 8)
+
+#define XSP_USBPHYACR5 ((SSUSB_SIFSLV_U2PHY_COM) + 0x014)
+#define P2A5_RG_HSTX_SRCAL_EN BIT(15)
+#define P2A5_RG_HSTX_SRCTRL GENMASK(14, 12)
+#define P2A5_RG_HSTX_SRCTRL_VAL(x) ((0x7 & (x)) << 12)
+
+#define XSP_USBPHYACR6 ((SSUSB_SIFSLV_U2PHY_COM) + 0x018)
+#define P2A6_RG_BC11_SW_EN BIT(23)
+#define P2A6_RG_OTG_VBUSCMP_EN BIT(20)
+
+#define XSP_U2PHYDTM1 ((SSUSB_SIFSLV_U2PHY_COM) + 0x06C)
+#define P2D_FORCE_IDDIG BIT(9)
+#define P2D_RG_VBUSVALID BIT(5)
+#define P2D_RG_SESSEND BIT(4)
+#define P2D_RG_AVALID BIT(2)
+#define P2D_RG_IDDIG BIT(1)
+
+#define SSPXTP_PHYA_GLB_00 ((SSPXTP_SIFSLV_PHYA_GLB) + 0x00)
+#define RG_XTP_GLB_BIAS_INTR_CTRL GENMASK(21, 16)
+#define RG_XTP_GLB_BIAS_INTR_CTRL_VAL(x) ((0x3f & (x)) << 16)
+
+#define SSPXTP_PHYA_LN_04 ((SSPXTP_SIFSLV_PHYA_LN) + 0x04)
+#define RG_XTP_LN0_TX_IMPSEL GENMASK(4, 0)
+#define RG_XTP_LN0_TX_IMPSEL_VAL(x) (0x1f & (x))
+
+#define SSPXTP_PHYA_LN_14 ((SSPXTP_SIFSLV_PHYA_LN) + 0x014)
+#define RG_XTP_LN0_RX_IMPSEL GENMASK(4, 0)
+#define RG_XTP_LN0_RX_IMPSEL_VAL(x) (0x1f & (x))
+
+#define XSP_REF_CLK 26 /* MHZ */
+#define XSP_SLEW_RATE_COEF 17
+#define XSP_SR_COEF_DIVISOR 1000
+#define XSP_FM_DET_CYCLE_CNT 1024
+
+struct xsphy_instance {
+ struct phy *phy;
+ void __iomem *port_base;
+ struct clk *ref_clk; /* reference clock of anolog phy */
+ u32 index;
+ u32 type;
+ /* only for HQA test */
+ int efuse_intr;
+ int efuse_tx_imp;
+ int efuse_rx_imp;
+ /* u2 eye diagram */
+ int eye_src;
+ int eye_vrt;
+ int eye_term;
+};
+
+struct mtk_xsphy {
+ struct device *dev;
+ void __iomem *glb_base; /* only shared u3 sif */
+ struct xsphy_instance **phys;
+ int nphys;
+ int src_ref_clk; /* MHZ, reference clock for slew rate calibrate */
+ int src_coef; /* coefficient for slew rate calibrate */
+};
+
+static void u2_phy_slew_rate_calibrate(struct mtk_xsphy *xsphy,
+ struct xsphy_instance *inst)
+{
+ void __iomem *pbase = inst->port_base;
+ int calib_val;
+ int fm_out;
+ u32 tmp;
+
+ /* use force value */
+ if (inst->eye_src)
+ return;
+
+ /* enable USB ring oscillator */
+ tmp = readl(pbase + XSP_USBPHYACR5);
+ tmp |= P2A5_RG_HSTX_SRCAL_EN;
+ writel(tmp, pbase + XSP_USBPHYACR5);
+ udelay(1); /* wait clock stable */
+
+ /* enable free run clock */
+ tmp = readl(pbase + XSP_U2FREQ_FMMONR1);
+ tmp |= P2F_RG_FRCK_EN;
+ writel(tmp, pbase + XSP_U2FREQ_FMMONR1);
+
+ /* set cycle count as 1024 */
+ tmp = readl(pbase + XSP_U2FREQ_FMCR0);
+ tmp &= ~(P2F_RG_CYCLECNT);
+ tmp |= P2F_RG_CYCLECNT_VAL(XSP_FM_DET_CYCLE_CNT);
+ writel(tmp, pbase + XSP_U2FREQ_FMCR0);
+
+ /* enable frequency meter */
+ tmp = readl(pbase + XSP_U2FREQ_FMCR0);
+ tmp |= P2F_RG_FREQDET_EN;
+ writel(tmp, pbase + XSP_U2FREQ_FMCR0);
+
+ /* ignore return value */
+ readl_poll_timeout(pbase + XSP_U2FREQ_FMMONR1, tmp,
+ (tmp & P2F_USB_FM_VALID), 10, 200);
+
+ fm_out = readl(pbase + XSP_U2FREQ_MMONR0);
+
+ /* disable frequency meter */
+ tmp = readl(pbase + XSP_U2FREQ_FMCR0);
+ tmp &= ~P2F_RG_FREQDET_EN;
+ writel(tmp, pbase + XSP_U2FREQ_FMCR0);
+
+ /* disable free run clock */
+ tmp = readl(pbase + XSP_U2FREQ_FMMONR1);
+ tmp &= ~P2F_RG_FRCK_EN;
+ writel(tmp, pbase + XSP_U2FREQ_FMMONR1);
+
+ if (fm_out) {
+ /* (1024 / FM_OUT) x reference clock frequency x coefficient */
+ tmp = xsphy->src_ref_clk * xsphy->src_coef;
+ tmp = (tmp * XSP_FM_DET_CYCLE_CNT) / fm_out;
+ calib_val = DIV_ROUND_CLOSEST(tmp, XSP_SR_COEF_DIVISOR);
+ } else {
+ /* if FM detection fail, set default value */
+ calib_val = 3;
+ }
+ dev_dbg(xsphy->dev, "phy.%d, fm_out:%d, calib:%d (clk:%d, coef:%d)\n",
+ inst->index, fm_out, calib_val,
+ xsphy->src_ref_clk, xsphy->src_coef);
+
+ /* set HS slew rate */
+ tmp = readl(pbase + XSP_USBPHYACR5);
+ tmp &= ~P2A5_RG_HSTX_SRCTRL;
+ tmp |= P2A5_RG_HSTX_SRCTRL_VAL(calib_val);
+ writel(tmp, pbase + XSP_USBPHYACR5);
+
+ /* disable USB ring oscillator */
+ tmp = readl(pbase + XSP_USBPHYACR5);
+ tmp &= ~P2A5_RG_HSTX_SRCAL_EN;
+ writel(tmp, pbase + XSP_USBPHYACR5);
+}
+
+static void u2_phy_instance_init(struct mtk_xsphy *xsphy,
+ struct xsphy_instance *inst)
+{
+ void __iomem *pbase = inst->port_base;
+ u32 tmp;
+
+ /* DP/DM BC1.1 path Disable */
+ tmp = readl(pbase + XSP_USBPHYACR6);
+ tmp &= ~P2A6_RG_BC11_SW_EN;
+ writel(tmp, pbase + XSP_USBPHYACR6);
+
+ tmp = readl(pbase + XSP_USBPHYACR0);
+ tmp |= P2A0_RG_INTR_EN;
+ writel(tmp, pbase + XSP_USBPHYACR0);
+}
+
+static void u2_phy_instance_power_on(struct mtk_xsphy *xsphy,
+ struct xsphy_instance *inst)
+{
+ void __iomem *pbase = inst->port_base;
+ u32 index = inst->index;
+ u32 tmp;
+
+ tmp = readl(pbase + XSP_USBPHYACR6);
+ tmp |= P2A6_RG_OTG_VBUSCMP_EN;
+ writel(tmp, pbase + XSP_USBPHYACR6);
+
+ tmp = readl(pbase + XSP_U2PHYDTM1);
+ tmp |= P2D_RG_VBUSVALID | P2D_RG_AVALID;
+ tmp &= ~P2D_RG_SESSEND;
+ writel(tmp, pbase + XSP_U2PHYDTM1);
+
+ dev_dbg(xsphy->dev, "%s(%d)\n", __func__, index);
+}
+
+static void u2_phy_instance_power_off(struct mtk_xsphy *xsphy,
+ struct xsphy_instance *inst)
+{
+ void __iomem *pbase = inst->port_base;
+ u32 index = inst->index;
+ u32 tmp;
+
+ tmp = readl(pbase + XSP_USBPHYACR6);
+ tmp &= ~P2A6_RG_OTG_VBUSCMP_EN;
+ writel(tmp, pbase + XSP_USBPHYACR6);
+
+ tmp = readl(pbase + XSP_U2PHYDTM1);
+ tmp &= ~(P2D_RG_VBUSVALID | P2D_RG_AVALID);
+ tmp |= P2D_RG_SESSEND;
+ writel(tmp, pbase + XSP_U2PHYDTM1);
+
+ dev_dbg(xsphy->dev, "%s(%d)\n", __func__, index);
+}
+
+static void u2_phy_instance_set_mode(struct mtk_xsphy *xsphy,
+ struct xsphy_instance *inst,
+ enum phy_mode mode)
+{
+ u32 tmp;
+
+ tmp = readl(inst->port_base + XSP_U2PHYDTM1);
+ switch (mode) {
+ case PHY_MODE_USB_DEVICE:
+ tmp |= P2D_FORCE_IDDIG | P2D_RG_IDDIG;
+ break;
+ case PHY_MODE_USB_HOST:
+ tmp |= P2D_FORCE_IDDIG;
+ tmp &= ~P2D_RG_IDDIG;
+ break;
+ case PHY_MODE_USB_OTG:
+ tmp &= ~(P2D_FORCE_IDDIG | P2D_RG_IDDIG);
+ break;
+ default:
+ return;
+ }
+ writel(tmp, inst->port_base + XSP_U2PHYDTM1);
+}
+
+static void phy_parse_property(struct mtk_xsphy *xsphy,
+ struct xsphy_instance *inst)
+{
+ struct device *dev = &inst->phy->dev;
+
+ switch (inst->type) {
+ case PHY_TYPE_USB2:
+ device_property_read_u32(dev, "mediatek,efuse-intr",
+ &inst->efuse_intr);
+ device_property_read_u32(dev, "mediatek,eye-src",
+ &inst->eye_src);
+ device_property_read_u32(dev, "mediatek,eye-vrt",
+ &inst->eye_vrt);
+ device_property_read_u32(dev, "mediatek,eye-term",
+ &inst->eye_term);
+ dev_dbg(dev, "intr:%d, src:%d, vrt:%d, term:%d\n",
+ inst->efuse_intr, inst->eye_src,
+ inst->eye_vrt, inst->eye_term);
+ break;
+ case PHY_TYPE_USB3:
+ device_property_read_u32(dev, "mediatek,efuse-intr",
+ &inst->efuse_intr);
+ device_property_read_u32(dev, "mediatek,efuse-tx-imp",
+ &inst->efuse_tx_imp);
+ device_property_read_u32(dev, "mediatek,efuse-rx-imp",
+ &inst->efuse_rx_imp);
+ dev_dbg(dev, "intr:%d, tx-imp:%d, rx-imp:%d\n",
+ inst->efuse_intr, inst->efuse_tx_imp,
+ inst->efuse_rx_imp);
+ break;
+ default:
+ dev_err(xsphy->dev, "incompatible phy type\n");
+ return;
+ }
+}
+
+static void u2_phy_props_set(struct mtk_xsphy *xsphy,
+ struct xsphy_instance *inst)
+{
+ void __iomem *pbase = inst->port_base;
+ u32 tmp;
+
+ if (inst->efuse_intr) {
+ tmp = readl(pbase + XSP_USBPHYACR1);
+ tmp &= ~P2A1_RG_INTR_CAL;
+ tmp |= P2A1_RG_INTR_CAL_VAL(inst->efuse_intr);
+ writel(tmp, pbase + XSP_USBPHYACR1);
+ }
+
+ if (inst->eye_src) {
+ tmp = readl(pbase + XSP_USBPHYACR5);
+ tmp &= ~P2A5_RG_HSTX_SRCTRL;
+ tmp |= P2A5_RG_HSTX_SRCTRL_VAL(inst->eye_src);
+ writel(tmp, pbase + XSP_USBPHYACR5);
+ }
+
+ if (inst->eye_vrt) {
+ tmp = readl(pbase + XSP_USBPHYACR1);
+ tmp &= ~P2A1_RG_VRT_SEL;
+ tmp |= P2A1_RG_VRT_SEL_VAL(inst->eye_vrt);
+ writel(tmp, pbase + XSP_USBPHYACR1);
+ }
+
+ if (inst->eye_term) {
+ tmp = readl(pbase + XSP_USBPHYACR1);
+ tmp &= ~P2A1_RG_TERM_SEL;
+ tmp |= P2A1_RG_TERM_SEL_VAL(inst->eye_term);
+ writel(tmp, pbase + XSP_USBPHYACR1);
+ }
+}
+
+static void u3_phy_props_set(struct mtk_xsphy *xsphy,
+ struct xsphy_instance *inst)
+{
+ void __iomem *pbase = inst->port_base;
+ u32 tmp;
+
+ if (inst->efuse_intr) {
+ tmp = readl(xsphy->glb_base + SSPXTP_PHYA_GLB_00);
+ tmp &= ~RG_XTP_GLB_BIAS_INTR_CTRL;
+ tmp |= RG_XTP_GLB_BIAS_INTR_CTRL_VAL(inst->efuse_intr);
+ writel(tmp, xsphy->glb_base + SSPXTP_PHYA_GLB_00);
+ }
+
+ if (inst->efuse_tx_imp) {
+ tmp = readl(pbase + SSPXTP_PHYA_LN_04);
+ tmp &= ~RG_XTP_LN0_TX_IMPSEL;
+ tmp |= RG_XTP_LN0_TX_IMPSEL_VAL(inst->efuse_tx_imp);
+ writel(tmp, pbase + SSPXTP_PHYA_LN_04);
+ }
+
+ if (inst->efuse_rx_imp) {
+ tmp = readl(pbase + SSPXTP_PHYA_LN_14);
+ tmp &= ~RG_XTP_LN0_RX_IMPSEL;
+ tmp |= RG_XTP_LN0_RX_IMPSEL_VAL(inst->efuse_rx_imp);
+ writel(tmp, pbase + SSPXTP_PHYA_LN_14);
+ }
+}
+
+static int mtk_phy_init(struct phy *phy)
+{
+ struct xsphy_instance *inst = phy_get_drvdata(phy);
+ struct mtk_xsphy *xsphy = dev_get_drvdata(phy->dev.parent);
+ int ret;
+
+ ret = clk_prepare_enable(inst->ref_clk);
+ if (ret) {
+ dev_err(xsphy->dev, "failed to enable ref_clk\n");
+ return ret;
+ }
+
+ switch (inst->type) {
+ case PHY_TYPE_USB2:
+ u2_phy_instance_init(xsphy, inst);
+ u2_phy_props_set(xsphy, inst);
+ break;
+ case PHY_TYPE_USB3:
+ u3_phy_props_set(xsphy, inst);
+ break;
+ default:
+ dev_err(xsphy->dev, "incompatible phy type\n");
+ clk_disable_unprepare(inst->ref_clk);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mtk_phy_power_on(struct phy *phy)
+{
+ struct xsphy_instance *inst = phy_get_drvdata(phy);
+ struct mtk_xsphy *xsphy = dev_get_drvdata(phy->dev.parent);
+
+ if (inst->type == PHY_TYPE_USB2) {
+ u2_phy_instance_power_on(xsphy, inst);
+ u2_phy_slew_rate_calibrate(xsphy, inst);
+ }
+
+ return 0;
+}
+
+static int mtk_phy_power_off(struct phy *phy)
+{
+ struct xsphy_instance *inst = phy_get_drvdata(phy);
+ struct mtk_xsphy *xsphy = dev_get_drvdata(phy->dev.parent);
+
+ if (inst->type == PHY_TYPE_USB2)
+ u2_phy_instance_power_off(xsphy, inst);
+
+ return 0;
+}
+
+static int mtk_phy_exit(struct phy *phy)
+{
+ struct xsphy_instance *inst = phy_get_drvdata(phy);
+
+ clk_disable_unprepare(inst->ref_clk);
+ return 0;
+}
+
+static int mtk_phy_set_mode(struct phy *phy, enum phy_mode mode)
+{
+ struct xsphy_instance *inst = phy_get_drvdata(phy);
+ struct mtk_xsphy *xsphy = dev_get_drvdata(phy->dev.parent);
+
+ if (inst->type == PHY_TYPE_USB2)
+ u2_phy_instance_set_mode(xsphy, inst, mode);
+
+ return 0;
+}
+
+static struct phy *mtk_phy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct mtk_xsphy *xsphy = dev_get_drvdata(dev);
+ struct xsphy_instance *inst = NULL;
+ struct device_node *phy_np = args->np;
+ int index;
+
+ if (args->args_count != 1) {
+ dev_err(dev, "invalid number of cells in 'phy' property\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ for (index = 0; index < xsphy->nphys; index++)
+ if (phy_np == xsphy->phys[index]->phy->dev.of_node) {
+ inst = xsphy->phys[index];
+ break;
+ }
+
+ if (!inst) {
+ dev_err(dev, "failed to find appropriate phy\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ inst->type = args->args[0];
+ if (!(inst->type == PHY_TYPE_USB2 ||
+ inst->type == PHY_TYPE_USB3)) {
+ dev_err(dev, "unsupported phy type: %d\n", inst->type);
+ return ERR_PTR(-EINVAL);
+ }
+
+ phy_parse_property(xsphy, inst);
+
+ return inst->phy;
+}
+
+static const struct phy_ops mtk_xsphy_ops = {
+ .init = mtk_phy_init,
+ .exit = mtk_phy_exit,
+ .power_on = mtk_phy_power_on,
+ .power_off = mtk_phy_power_off,
+ .set_mode = mtk_phy_set_mode,
+ .owner = THIS_MODULE,
+};
+
+static const struct of_device_id mtk_xsphy_id_table[] = {
+ { .compatible = "mediatek,xsphy", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, mtk_xsphy_id_table);
+
+static int mtk_xsphy_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 resource *glb_res;
+ struct mtk_xsphy *xsphy;
+ struct resource res;
+ int port, retval;
+
+ xsphy = devm_kzalloc(dev, sizeof(*xsphy), GFP_KERNEL);
+ if (!xsphy)
+ return -ENOMEM;
+
+ xsphy->nphys = of_get_child_count(np);
+ xsphy->phys = devm_kcalloc(dev, xsphy->nphys,
+ sizeof(*xsphy->phys), GFP_KERNEL);
+ if (!xsphy->phys)
+ return -ENOMEM;
+
+ xsphy->dev = dev;
+ platform_set_drvdata(pdev, xsphy);
+
+ glb_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ /* optional, may not exist if no u3 phys */
+ if (glb_res) {
+ /* get banks shared by multiple u3 phys */
+ xsphy->glb_base = devm_ioremap_resource(dev, glb_res);
+ if (IS_ERR(xsphy->glb_base)) {
+ dev_err(dev, "failed to remap glb regs\n");
+ return PTR_ERR(xsphy->glb_base);
+ }
+ }
+
+ xsphy->src_ref_clk = XSP_REF_CLK;
+ xsphy->src_coef = XSP_SLEW_RATE_COEF;
+ /* update parameters of slew rate calibrate if exist */
+ device_property_read_u32(dev, "mediatek,src-ref-clk-mhz",
+ &xsphy->src_ref_clk);
+ device_property_read_u32(dev, "mediatek,src-coef", &xsphy->src_coef);
+
+ port = 0;
+ for_each_child_of_node(np, child_np) {
+ struct xsphy_instance *inst;
+ struct phy *phy;
+
+ inst = devm_kzalloc(dev, sizeof(*inst), GFP_KERNEL);
+ if (!inst) {
+ retval = -ENOMEM;
+ goto put_child;
+ }
+
+ xsphy->phys[port] = inst;
+
+ phy = devm_phy_create(dev, child_np, &mtk_xsphy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create phy\n");
+ retval = PTR_ERR(phy);
+ goto put_child;
+ }
+
+ retval = of_address_to_resource(child_np, 0, &res);
+ if (retval) {
+ dev_err(dev, "failed to get address resource(id-%d)\n",
+ port);
+ goto put_child;
+ }
+
+ inst->port_base = devm_ioremap_resource(&phy->dev, &res);
+ if (IS_ERR(inst->port_base)) {
+ dev_err(dev, "failed to remap phy regs\n");
+ retval = PTR_ERR(inst->port_base);
+ goto put_child;
+ }
+
+ inst->phy = phy;
+ inst->index = port;
+ phy_set_drvdata(phy, inst);
+ port++;
+
+ inst->ref_clk = devm_clk_get(&phy->dev, "ref");
+ if (IS_ERR(inst->ref_clk)) {
+ dev_err(dev, "failed to get ref_clk(id-%d)\n", port);
+ retval = PTR_ERR(inst->ref_clk);
+ goto put_child;
+ }
+ }
+
+ provider = devm_of_phy_provider_register(dev, mtk_phy_xlate);
+ return PTR_ERR_OR_ZERO(provider);
+
+put_child:
+ of_node_put(child_np);
+ return retval;
+}
+
+static struct platform_driver mtk_xsphy_driver = {
+ .probe = mtk_xsphy_probe,
+ .driver = {
+ .name = "mtk-xsphy",
+ .of_match_table = mtk_xsphy_id_table,
+ },
+};
+
+module_platform_driver(mtk_xsphy_driver);
+
+MODULE_AUTHOR("Chunfeng Yun <chunfeng.yun@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek USB XS-PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/motorola/Kconfig b/drivers/phy/motorola/Kconfig
new file mode 100644
index 000000000..718f87297
--- /dev/null
+++ b/drivers/phy/motorola/Kconfig
@@ -0,0 +1,20 @@
+#
+# Phy drivers for Motorola devices
+#
+config PHY_CPCAP_USB
+ tristate "CPCAP PMIC USB PHY driver"
+ depends on USB_SUPPORT && IIO
+ depends on USB_MUSB_HDRC || USB_MUSB_HDRC=n
+ select GENERIC_PHY
+ select USB_PHY
+ help
+ Enable this for USB to work on Motorola phones and tablets
+ such as Droid 4.
+
+config PHY_MAPPHONE_MDM6600
+ tristate "Motorola Mapphone MDM6600 modem USB PHY driver"
+ depends on OF && USB_SUPPORT && GPIOLIB
+ select GENERIC_PHY
+ help
+ Enable this for MDM6600 USB modem to work on Motorola phones
+ and tablets such as Droid 4.
diff --git a/drivers/phy/motorola/Makefile b/drivers/phy/motorola/Makefile
new file mode 100644
index 000000000..3514f985b
--- /dev/null
+++ b/drivers/phy/motorola/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for the phy drivers.
+#
+
+obj-$(CONFIG_PHY_CPCAP_USB) += phy-cpcap-usb.o
+obj-$(CONFIG_PHY_MAPPHONE_MDM6600) += phy-mapphone-mdm6600.o
diff --git a/drivers/phy/motorola/phy-cpcap-usb.c b/drivers/phy/motorola/phy-cpcap-usb.c
new file mode 100644
index 000000000..106f53f33
--- /dev/null
+++ b/drivers/phy/motorola/phy-cpcap-usb.c
@@ -0,0 +1,698 @@
+/*
+ * Motorola CPCAP PMIC USB PHY driver
+ * Copyright (C) 2017 Tony Lindgren <tony@atomide.com>
+ *
+ * Some parts based on earlier Motorola Linux kernel tree code in
+ * board-mapphone-usb.c and cpcap-usb-det.c:
+ * Copyright (C) 2007 - 2011 Motorola, 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/atomic.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/iio/consumer.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include <linux/gpio/consumer.h>
+#include <linux/mfd/motorola-cpcap.h>
+#include <linux/phy/omap_usb.h>
+#include <linux/phy/phy.h>
+#include <linux/regulator/consumer.h>
+#include <linux/usb/musb.h>
+
+/* CPCAP_REG_USBC1 register bits */
+#define CPCAP_BIT_IDPULSE BIT(15)
+#define CPCAP_BIT_ID100KPU BIT(14)
+#define CPCAP_BIT_IDPUCNTRL BIT(13)
+#define CPCAP_BIT_IDPU BIT(12)
+#define CPCAP_BIT_IDPD BIT(11)
+#define CPCAP_BIT_VBUSCHRGTMR3 BIT(10)
+#define CPCAP_BIT_VBUSCHRGTMR2 BIT(9)
+#define CPCAP_BIT_VBUSCHRGTMR1 BIT(8)
+#define CPCAP_BIT_VBUSCHRGTMR0 BIT(7)
+#define CPCAP_BIT_VBUSPU BIT(6)
+#define CPCAP_BIT_VBUSPD BIT(5)
+#define CPCAP_BIT_DMPD BIT(4)
+#define CPCAP_BIT_DPPD BIT(3)
+#define CPCAP_BIT_DM1K5PU BIT(2)
+#define CPCAP_BIT_DP1K5PU BIT(1)
+#define CPCAP_BIT_DP150KPU BIT(0)
+
+/* CPCAP_REG_USBC2 register bits */
+#define CPCAP_BIT_ZHSDRV1 BIT(15)
+#define CPCAP_BIT_ZHSDRV0 BIT(14)
+#define CPCAP_BIT_DPLLCLKREQ BIT(13)
+#define CPCAP_BIT_SE0CONN BIT(12)
+#define CPCAP_BIT_UARTTXTRI BIT(11)
+#define CPCAP_BIT_UARTSWAP BIT(10)
+#define CPCAP_BIT_UARTMUX1 BIT(9)
+#define CPCAP_BIT_UARTMUX0 BIT(8)
+#define CPCAP_BIT_ULPISTPLOW BIT(7)
+#define CPCAP_BIT_TXENPOL BIT(6)
+#define CPCAP_BIT_USBXCVREN BIT(5)
+#define CPCAP_BIT_USBCNTRL BIT(4)
+#define CPCAP_BIT_USBSUSPEND BIT(3)
+#define CPCAP_BIT_EMUMODE2 BIT(2)
+#define CPCAP_BIT_EMUMODE1 BIT(1)
+#define CPCAP_BIT_EMUMODE0 BIT(0)
+
+/* CPCAP_REG_USBC3 register bits */
+#define CPCAP_BIT_SPARE_898_15 BIT(15)
+#define CPCAP_BIT_IHSTX03 BIT(14)
+#define CPCAP_BIT_IHSTX02 BIT(13)
+#define CPCAP_BIT_IHSTX01 BIT(12)
+#define CPCAP_BIT_IHSTX0 BIT(11)
+#define CPCAP_BIT_IDPU_SPI BIT(10)
+#define CPCAP_BIT_UNUSED_898_9 BIT(9)
+#define CPCAP_BIT_VBUSSTBY_EN BIT(8)
+#define CPCAP_BIT_VBUSEN_SPI BIT(7)
+#define CPCAP_BIT_VBUSPU_SPI BIT(6)
+#define CPCAP_BIT_VBUSPD_SPI BIT(5)
+#define CPCAP_BIT_DMPD_SPI BIT(4)
+#define CPCAP_BIT_DPPD_SPI BIT(3)
+#define CPCAP_BIT_SUSPEND_SPI BIT(2)
+#define CPCAP_BIT_PU_SPI BIT(1)
+#define CPCAP_BIT_ULPI_SPI_SEL BIT(0)
+
+struct cpcap_usb_ints_state {
+ bool id_ground;
+ bool id_float;
+ bool chrg_det;
+ bool rvrs_chrg;
+ bool vbusov;
+
+ bool chrg_se1b;
+ bool se0conn;
+ bool rvrs_mode;
+ bool chrgcurr1;
+ bool vbusvld;
+ bool sessvld;
+ bool sessend;
+ bool se1;
+
+ bool battdetb;
+ bool dm;
+ bool dp;
+};
+
+enum cpcap_gpio_mode {
+ CPCAP_DM_DP,
+ CPCAP_MDM_RX_TX,
+ CPCAP_UNKNOWN_DISABLED, /* Seems to disable USB lines */
+ CPCAP_OTG_DM_DP,
+};
+
+struct cpcap_phy_ddata {
+ struct regmap *reg;
+ struct device *dev;
+ struct clk *refclk;
+ struct usb_phy phy;
+ struct delayed_work detect_work;
+ struct pinctrl *pins;
+ struct pinctrl_state *pins_ulpi;
+ struct pinctrl_state *pins_utmi;
+ struct pinctrl_state *pins_uart;
+ struct gpio_desc *gpio[2];
+ struct iio_channel *vbus;
+ struct iio_channel *id;
+ struct regulator *vusb;
+ atomic_t active;
+};
+
+static bool cpcap_usb_vbus_valid(struct cpcap_phy_ddata *ddata)
+{
+ int error, value = 0;
+
+ error = iio_read_channel_processed(ddata->vbus, &value);
+ if (error >= 0)
+ return value > 3900 ? true : false;
+
+ dev_err(ddata->dev, "error reading VBUS: %i\n", error);
+
+ return false;
+}
+
+static int cpcap_usb_phy_set_host(struct usb_otg *otg, struct usb_bus *host)
+{
+ otg->host = host;
+ if (!host)
+ otg->state = OTG_STATE_UNDEFINED;
+
+ return 0;
+}
+
+static int cpcap_usb_phy_set_peripheral(struct usb_otg *otg,
+ struct usb_gadget *gadget)
+{
+ otg->gadget = gadget;
+ if (!gadget)
+ otg->state = OTG_STATE_UNDEFINED;
+
+ return 0;
+}
+
+static const struct phy_ops ops = {
+ .owner = THIS_MODULE,
+};
+
+static int cpcap_phy_get_ints_state(struct cpcap_phy_ddata *ddata,
+ struct cpcap_usb_ints_state *s)
+{
+ int val, error;
+
+ error = regmap_read(ddata->reg, CPCAP_REG_INTS1, &val);
+ if (error)
+ return error;
+
+ s->id_ground = val & BIT(15);
+ s->id_float = val & BIT(14);
+ s->vbusov = val & BIT(11);
+
+ error = regmap_read(ddata->reg, CPCAP_REG_INTS2, &val);
+ if (error)
+ return error;
+
+ s->vbusvld = val & BIT(3);
+ s->sessvld = val & BIT(2);
+ s->sessend = val & BIT(1);
+ s->se1 = val & BIT(0);
+
+ error = regmap_read(ddata->reg, CPCAP_REG_INTS4, &val);
+ if (error)
+ return error;
+
+ s->dm = val & BIT(1);
+ s->dp = val & BIT(0);
+
+ return 0;
+}
+
+static int cpcap_usb_set_uart_mode(struct cpcap_phy_ddata *ddata);
+static int cpcap_usb_set_usb_mode(struct cpcap_phy_ddata *ddata);
+
+static void cpcap_usb_try_musb_mailbox(struct cpcap_phy_ddata *ddata,
+ enum musb_vbus_id_status status)
+{
+ int error;
+
+ error = musb_mailbox(status);
+ if (!error)
+ return;
+
+ dev_dbg(ddata->dev, "%s: musb_mailbox failed: %i\n",
+ __func__, error);
+}
+
+static void cpcap_usb_detect(struct work_struct *work)
+{
+ struct cpcap_phy_ddata *ddata;
+ struct cpcap_usb_ints_state s;
+ bool vbus = false;
+ int error;
+
+ ddata = container_of(work, struct cpcap_phy_ddata, detect_work.work);
+
+ error = cpcap_phy_get_ints_state(ddata, &s);
+ if (error)
+ return;
+
+ if (s.id_ground) {
+ dev_dbg(ddata->dev, "id ground, USB host mode\n");
+ error = cpcap_usb_set_usb_mode(ddata);
+ if (error)
+ goto out_err;
+
+ cpcap_usb_try_musb_mailbox(ddata, MUSB_ID_GROUND);
+
+ error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3,
+ CPCAP_BIT_VBUSSTBY_EN,
+ CPCAP_BIT_VBUSSTBY_EN);
+ if (error)
+ goto out_err;
+
+ return;
+ }
+
+ error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3,
+ CPCAP_BIT_VBUSSTBY_EN, 0);
+ if (error)
+ goto out_err;
+
+ vbus = cpcap_usb_vbus_valid(ddata);
+
+ if (vbus) {
+ /* Are we connected to a docking station with vbus? */
+ if (s.id_ground) {
+ dev_dbg(ddata->dev, "connected to a dock\n");
+
+ /* No VBUS needed with docks */
+ error = cpcap_usb_set_usb_mode(ddata);
+ if (error)
+ goto out_err;
+ cpcap_usb_try_musb_mailbox(ddata, MUSB_ID_GROUND);
+
+ return;
+ }
+
+ /* Otherwise assume we're connected to a USB host */
+ dev_dbg(ddata->dev, "connected to USB host\n");
+ error = cpcap_usb_set_usb_mode(ddata);
+ if (error)
+ goto out_err;
+ cpcap_usb_try_musb_mailbox(ddata, MUSB_VBUS_VALID);
+
+ return;
+ }
+
+ cpcap_usb_try_musb_mailbox(ddata, MUSB_VBUS_OFF);
+
+ /* Default to debug UART mode */
+ error = cpcap_usb_set_uart_mode(ddata);
+ if (error)
+ goto out_err;
+
+ dev_dbg(ddata->dev, "set UART mode\n");
+
+ return;
+
+out_err:
+ dev_err(ddata->dev, "error setting cable state: %i\n", error);
+}
+
+static irqreturn_t cpcap_phy_irq_thread(int irq, void *data)
+{
+ struct cpcap_phy_ddata *ddata = data;
+
+ if (!atomic_read(&ddata->active))
+ return IRQ_NONE;
+
+ schedule_delayed_work(&ddata->detect_work, msecs_to_jiffies(1));
+
+ return IRQ_HANDLED;
+}
+
+static int cpcap_usb_init_irq(struct platform_device *pdev,
+ struct cpcap_phy_ddata *ddata,
+ const char *name)
+{
+ int irq, error;
+
+ irq = platform_get_irq_byname(pdev, name);
+ if (irq < 0)
+ return -ENODEV;
+
+ error = devm_request_threaded_irq(ddata->dev, irq, NULL,
+ cpcap_phy_irq_thread,
+ IRQF_SHARED,
+ name, ddata);
+ if (error) {
+ dev_err(ddata->dev, "could not get irq %s: %i\n",
+ name, error);
+
+ return error;
+ }
+
+ return 0;
+}
+
+static const char * const cpcap_phy_irqs[] = {
+ /* REG_INT_0 */
+ "id_ground", "id_float",
+
+ /* REG_INT1 */
+ "se0conn", "vbusvld", "sessvld", "sessend", "se1",
+
+ /* REG_INT_3 */
+ "dm", "dp",
+};
+
+static int cpcap_usb_init_interrupts(struct platform_device *pdev,
+ struct cpcap_phy_ddata *ddata)
+{
+ int i, error;
+
+ for (i = 0; i < ARRAY_SIZE(cpcap_phy_irqs); i++) {
+ error = cpcap_usb_init_irq(pdev, ddata, cpcap_phy_irqs[i]);
+ if (error)
+ return error;
+ }
+
+ return 0;
+}
+
+/*
+ * Optional pins and modes. At least Motorola mapphone devices
+ * are using two GPIOs and dynamic pinctrl to multiplex PHY pins
+ * to UART, ULPI or UTMI mode.
+ */
+
+static int cpcap_usb_gpio_set_mode(struct cpcap_phy_ddata *ddata,
+ enum cpcap_gpio_mode mode)
+{
+ if (!ddata->gpio[0] || !ddata->gpio[1])
+ return 0;
+
+ gpiod_set_value(ddata->gpio[0], mode & 1);
+ gpiod_set_value(ddata->gpio[1], mode >> 1);
+
+ return 0;
+}
+
+static int cpcap_usb_set_uart_mode(struct cpcap_phy_ddata *ddata)
+{
+ int error;
+
+ /* Disable lines to prevent glitches from waking up mdm6600 */
+ error = cpcap_usb_gpio_set_mode(ddata, CPCAP_UNKNOWN_DISABLED);
+ if (error)
+ goto out_err;
+
+ if (ddata->pins_uart) {
+ error = pinctrl_select_state(ddata->pins, ddata->pins_uart);
+ if (error)
+ goto out_err;
+ }
+
+ error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC1,
+ CPCAP_BIT_VBUSPD,
+ CPCAP_BIT_VBUSPD);
+ if (error)
+ goto out_err;
+
+ error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC2,
+ 0xffff, CPCAP_BIT_UARTMUX0 |
+ CPCAP_BIT_EMUMODE0);
+ if (error)
+ goto out_err;
+
+ error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3, 0x7fff,
+ CPCAP_BIT_IDPU_SPI);
+ if (error)
+ goto out_err;
+
+ /* Enable UART mode */
+ error = cpcap_usb_gpio_set_mode(ddata, CPCAP_DM_DP);
+ if (error)
+ goto out_err;
+
+ return 0;
+
+out_err:
+ dev_err(ddata->dev, "%s failed with %i\n", __func__, error);
+
+ return error;
+}
+
+static int cpcap_usb_set_usb_mode(struct cpcap_phy_ddata *ddata)
+{
+ int error;
+
+ /* Disable lines to prevent glitches from waking up mdm6600 */
+ error = cpcap_usb_gpio_set_mode(ddata, CPCAP_UNKNOWN_DISABLED);
+ if (error)
+ return error;
+
+ if (ddata->pins_utmi) {
+ error = pinctrl_select_state(ddata->pins, ddata->pins_utmi);
+ if (error) {
+ dev_err(ddata->dev, "could not set usb mode: %i\n",
+ error);
+
+ return error;
+ }
+ }
+
+ error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC1,
+ CPCAP_BIT_VBUSPD, 0);
+ if (error)
+ goto out_err;
+
+ error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC2,
+ CPCAP_BIT_USBXCVREN,
+ CPCAP_BIT_USBXCVREN);
+ if (error)
+ goto out_err;
+
+ error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3,
+ CPCAP_BIT_PU_SPI |
+ CPCAP_BIT_DMPD_SPI |
+ CPCAP_BIT_DPPD_SPI |
+ CPCAP_BIT_SUSPEND_SPI |
+ CPCAP_BIT_ULPI_SPI_SEL, 0);
+ if (error)
+ goto out_err;
+
+ error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC2,
+ CPCAP_BIT_USBXCVREN,
+ CPCAP_BIT_USBXCVREN);
+ if (error)
+ goto out_err;
+
+ /* Enable USB mode */
+ error = cpcap_usb_gpio_set_mode(ddata, CPCAP_OTG_DM_DP);
+ if (error)
+ goto out_err;
+
+ return 0;
+
+out_err:
+ dev_err(ddata->dev, "%s failed with %i\n", __func__, error);
+
+ return error;
+}
+
+static int cpcap_usb_init_optional_pins(struct cpcap_phy_ddata *ddata)
+{
+ ddata->pins = devm_pinctrl_get(ddata->dev);
+ if (IS_ERR(ddata->pins)) {
+ dev_info(ddata->dev, "default pins not configured: %ld\n",
+ PTR_ERR(ddata->pins));
+ ddata->pins = NULL;
+
+ return 0;
+ }
+
+ ddata->pins_ulpi = pinctrl_lookup_state(ddata->pins, "ulpi");
+ if (IS_ERR(ddata->pins_ulpi)) {
+ dev_info(ddata->dev, "ulpi pins not configured\n");
+ ddata->pins_ulpi = NULL;
+ }
+
+ ddata->pins_utmi = pinctrl_lookup_state(ddata->pins, "utmi");
+ if (IS_ERR(ddata->pins_utmi)) {
+ dev_info(ddata->dev, "utmi pins not configured\n");
+ ddata->pins_utmi = NULL;
+ }
+
+ ddata->pins_uart = pinctrl_lookup_state(ddata->pins, "uart");
+ if (IS_ERR(ddata->pins_uart)) {
+ dev_info(ddata->dev, "uart pins not configured\n");
+ ddata->pins_uart = NULL;
+ }
+
+ if (ddata->pins_uart)
+ return pinctrl_select_state(ddata->pins, ddata->pins_uart);
+
+ return 0;
+}
+
+static void cpcap_usb_init_optional_gpios(struct cpcap_phy_ddata *ddata)
+{
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ ddata->gpio[i] = devm_gpiod_get_index(ddata->dev, "mode",
+ i, GPIOD_OUT_HIGH);
+ if (IS_ERR(ddata->gpio[i])) {
+ dev_info(ddata->dev, "no mode change GPIO%i: %li\n",
+ i, PTR_ERR(ddata->gpio[i]));
+ ddata->gpio[i] = NULL;
+ }
+ }
+}
+
+static int cpcap_usb_init_iio(struct cpcap_phy_ddata *ddata)
+{
+ enum iio_chan_type type;
+ int error;
+
+ ddata->vbus = devm_iio_channel_get(ddata->dev, "vbus");
+ if (IS_ERR(ddata->vbus)) {
+ error = PTR_ERR(ddata->vbus);
+ goto out_err;
+ }
+
+ if (!ddata->vbus->indio_dev) {
+ error = -ENXIO;
+ goto out_err;
+ }
+
+ error = iio_get_channel_type(ddata->vbus, &type);
+ if (error < 0)
+ goto out_err;
+
+ if (type != IIO_VOLTAGE) {
+ error = -EINVAL;
+ goto out_err;
+ }
+
+ return 0;
+
+out_err:
+ dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n",
+ error);
+
+ return error;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id cpcap_usb_phy_id_table[] = {
+ {
+ .compatible = "motorola,cpcap-usb-phy",
+ },
+ {
+ .compatible = "motorola,mapphone-cpcap-usb-phy",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, cpcap_usb_phy_id_table);
+#endif
+
+static int cpcap_usb_phy_probe(struct platform_device *pdev)
+{
+ struct cpcap_phy_ddata *ddata;
+ struct phy *generic_phy;
+ struct phy_provider *phy_provider;
+ struct usb_otg *otg;
+ const struct of_device_id *of_id;
+ int error;
+
+ of_id = of_match_device(of_match_ptr(cpcap_usb_phy_id_table),
+ &pdev->dev);
+ if (!of_id)
+ return -EINVAL;
+
+ ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ ddata->reg = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!ddata->reg)
+ return -ENODEV;
+
+ otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL);
+ if (!otg)
+ return -ENOMEM;
+
+ ddata->dev = &pdev->dev;
+ ddata->phy.dev = ddata->dev;
+ ddata->phy.label = "cpcap_usb_phy";
+ ddata->phy.otg = otg;
+ ddata->phy.type = USB_PHY_TYPE_USB2;
+ otg->set_host = cpcap_usb_phy_set_host;
+ otg->set_peripheral = cpcap_usb_phy_set_peripheral;
+ otg->usb_phy = &ddata->phy;
+ INIT_DELAYED_WORK(&ddata->detect_work, cpcap_usb_detect);
+ platform_set_drvdata(pdev, ddata);
+
+ ddata->vusb = devm_regulator_get(&pdev->dev, "vusb");
+ if (IS_ERR(ddata->vusb))
+ return PTR_ERR(ddata->vusb);
+
+ error = regulator_enable(ddata->vusb);
+ if (error)
+ return error;
+
+ generic_phy = devm_phy_create(ddata->dev, NULL, &ops);
+ if (IS_ERR(generic_phy)) {
+ error = PTR_ERR(generic_phy);
+ goto out_reg_disable;
+ }
+
+ phy_set_drvdata(generic_phy, ddata);
+
+ phy_provider = devm_of_phy_provider_register(ddata->dev,
+ of_phy_simple_xlate);
+ if (IS_ERR(phy_provider)) {
+ error = PTR_ERR(phy_provider);
+ goto out_reg_disable;
+ }
+
+ error = cpcap_usb_init_optional_pins(ddata);
+ if (error)
+ goto out_reg_disable;
+
+ cpcap_usb_init_optional_gpios(ddata);
+
+ error = cpcap_usb_init_iio(ddata);
+ if (error)
+ goto out_reg_disable;
+
+ error = cpcap_usb_init_interrupts(pdev, ddata);
+ if (error)
+ goto out_reg_disable;
+
+ usb_add_phy_dev(&ddata->phy);
+ atomic_set(&ddata->active, 1);
+ schedule_delayed_work(&ddata->detect_work, msecs_to_jiffies(1));
+
+ return 0;
+
+out_reg_disable:
+ regulator_disable(ddata->vusb);
+
+ return error;
+}
+
+static int cpcap_usb_phy_remove(struct platform_device *pdev)
+{
+ struct cpcap_phy_ddata *ddata = platform_get_drvdata(pdev);
+ int error;
+
+ atomic_set(&ddata->active, 0);
+ error = cpcap_usb_set_uart_mode(ddata);
+ if (error)
+ dev_err(ddata->dev, "could not set UART mode\n");
+
+ cpcap_usb_try_musb_mailbox(ddata, MUSB_VBUS_OFF);
+
+ usb_remove_phy(&ddata->phy);
+ cancel_delayed_work_sync(&ddata->detect_work);
+ clk_unprepare(ddata->refclk);
+ regulator_disable(ddata->vusb);
+
+ return 0;
+}
+
+static struct platform_driver cpcap_usb_phy_driver = {
+ .probe = cpcap_usb_phy_probe,
+ .remove = cpcap_usb_phy_remove,
+ .driver = {
+ .name = "cpcap-usb-phy",
+ .of_match_table = of_match_ptr(cpcap_usb_phy_id_table),
+ },
+};
+
+module_platform_driver(cpcap_usb_phy_driver);
+
+MODULE_ALIAS("platform:cpcap_usb");
+MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
+MODULE_DESCRIPTION("CPCAP usb phy driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/motorola/phy-mapphone-mdm6600.c b/drivers/phy/motorola/phy-mapphone-mdm6600.c
new file mode 100644
index 000000000..77518010a
--- /dev/null
+++ b/drivers/phy/motorola/phy-mapphone-mdm6600.c
@@ -0,0 +1,648 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Motorola Mapphone MDM6600 modem GPIO controlled USB PHY driver
+ * Copyright (C) 2018 Tony Lindgren <tony@atomide.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/gpio/consumer.h>
+#include <linux/of_platform.h>
+#include <linux/phy/phy.h>
+
+#define PHY_MDM6600_PHY_DELAY_MS 4000 /* PHY enable 2.2s to 3.5s */
+#define PHY_MDM6600_ENABLED_DELAY_MS 8000 /* 8s more total for MDM6600 */
+#define PHY_MDM6600_WAKE_KICK_MS 600 /* time on after GPIO toggle */
+#define MDM6600_MODEM_IDLE_DELAY_MS 1000 /* modem after USB suspend */
+#define MDM6600_MODEM_WAKE_DELAY_MS 200 /* modem response after idle */
+
+enum phy_mdm6600_ctrl_lines {
+ PHY_MDM6600_ENABLE, /* USB PHY enable */
+ PHY_MDM6600_POWER, /* Device power */
+ PHY_MDM6600_RESET, /* Device reset */
+ PHY_MDM6600_NR_CTRL_LINES,
+};
+
+enum phy_mdm6600_bootmode_lines {
+ PHY_MDM6600_MODE0, /* out USB mode0 and OOB wake */
+ PHY_MDM6600_MODE1, /* out USB mode1, in OOB wake */
+ PHY_MDM6600_NR_MODE_LINES,
+};
+
+enum phy_mdm6600_cmd_lines {
+ PHY_MDM6600_CMD0,
+ PHY_MDM6600_CMD1,
+ PHY_MDM6600_CMD2,
+ PHY_MDM6600_NR_CMD_LINES,
+};
+
+enum phy_mdm6600_status_lines {
+ PHY_MDM6600_STATUS0,
+ PHY_MDM6600_STATUS1,
+ PHY_MDM6600_STATUS2,
+ PHY_MDM6600_NR_STATUS_LINES,
+};
+
+/*
+ * MDM6600 command codes. These are based on Motorola Mapphone Linux
+ * kernel tree.
+ */
+enum phy_mdm6600_cmd {
+ PHY_MDM6600_CMD_BP_PANIC_ACK,
+ PHY_MDM6600_CMD_DATA_ONLY_BYPASS, /* Reroute USB to CPCAP PHY */
+ PHY_MDM6600_CMD_FULL_BYPASS, /* Reroute USB to CPCAP PHY */
+ PHY_MDM6600_CMD_NO_BYPASS, /* Request normal USB mode */
+ PHY_MDM6600_CMD_BP_SHUTDOWN_REQ, /* Request device power off */
+ PHY_MDM6600_CMD_BP_UNKNOWN_5,
+ PHY_MDM6600_CMD_BP_UNKNOWN_6,
+ PHY_MDM6600_CMD_UNDEFINED,
+};
+
+/*
+ * MDM6600 status codes. These are based on Motorola Mapphone Linux
+ * kernel tree.
+ */
+enum phy_mdm6600_status {
+ PHY_MDM6600_STATUS_PANIC, /* Seems to be really off */
+ PHY_MDM6600_STATUS_PANIC_BUSY_WAIT,
+ PHY_MDM6600_STATUS_QC_DLOAD,
+ PHY_MDM6600_STATUS_RAM_DOWNLOADER, /* MDM6600 USB flashing mode */
+ PHY_MDM6600_STATUS_PHONE_CODE_AWAKE, /* MDM6600 normal USB mode */
+ PHY_MDM6600_STATUS_PHONE_CODE_ASLEEP,
+ PHY_MDM6600_STATUS_SHUTDOWN_ACK,
+ PHY_MDM6600_STATUS_UNDEFINED,
+};
+
+static const char * const
+phy_mdm6600_status_name[] = {
+ "off", "busy", "qc_dl", "ram_dl", "awake",
+ "asleep", "shutdown", "undefined",
+};
+
+struct phy_mdm6600 {
+ struct device *dev;
+ struct phy *generic_phy;
+ struct phy_provider *phy_provider;
+ struct gpio_desc *ctrl_gpios[PHY_MDM6600_NR_CTRL_LINES];
+ struct gpio_descs *mode_gpios;
+ struct gpio_descs *status_gpios;
+ struct gpio_descs *cmd_gpios;
+ struct delayed_work bootup_work;
+ struct delayed_work status_work;
+ struct delayed_work modem_wake_work;
+ struct completion ack;
+ bool enabled; /* mdm6600 phy enabled */
+ bool running; /* mdm6600 boot done */
+ bool awake; /* mdm6600 respnds on n_gsm */
+ int status;
+};
+
+static int phy_mdm6600_init(struct phy *x)
+{
+ struct phy_mdm6600 *ddata = phy_get_drvdata(x);
+ struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE];
+
+ if (!ddata->enabled)
+ return -EPROBE_DEFER;
+
+ gpiod_set_value_cansleep(enable_gpio, 0);
+
+ return 0;
+}
+
+static int phy_mdm6600_power_on(struct phy *x)
+{
+ struct phy_mdm6600 *ddata = phy_get_drvdata(x);
+ struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE];
+
+ if (!ddata->enabled)
+ return -ENODEV;
+
+ gpiod_set_value_cansleep(enable_gpio, 1);
+
+ return 0;
+}
+
+static int phy_mdm6600_power_off(struct phy *x)
+{
+ struct phy_mdm6600 *ddata = phy_get_drvdata(x);
+ struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE];
+
+ if (!ddata->enabled)
+ return -ENODEV;
+
+ gpiod_set_value_cansleep(enable_gpio, 0);
+
+ return 0;
+}
+
+static const struct phy_ops gpio_usb_ops = {
+ .init = phy_mdm6600_init,
+ .power_on = phy_mdm6600_power_on,
+ .power_off = phy_mdm6600_power_off,
+ .owner = THIS_MODULE,
+};
+
+/**
+ * phy_mdm6600_cmd() - send a command request to mdm6600
+ * @ddata: device driver data
+ *
+ * Configures the three command request GPIOs to the specified value.
+ */
+static void phy_mdm6600_cmd(struct phy_mdm6600 *ddata, int val)
+{
+ int values[PHY_MDM6600_NR_CMD_LINES];
+ int i;
+
+ val &= (1 << PHY_MDM6600_NR_CMD_LINES) - 1;
+ for (i = 0; i < PHY_MDM6600_NR_CMD_LINES; i++)
+ values[i] = (val & BIT(i)) >> i;
+
+ gpiod_set_array_value_cansleep(PHY_MDM6600_NR_CMD_LINES,
+ ddata->cmd_gpios->desc, values);
+}
+
+/**
+ * phy_mdm6600_status() - read mdm6600 status lines
+ * @ddata: device driver data
+ */
+static void phy_mdm6600_status(struct work_struct *work)
+{
+ struct phy_mdm6600 *ddata;
+ struct device *dev;
+ int values[PHY_MDM6600_NR_STATUS_LINES];
+ int error, i, val = 0;
+
+ ddata = container_of(work, struct phy_mdm6600, status_work.work);
+ dev = ddata->dev;
+
+ error = gpiod_get_array_value_cansleep(PHY_MDM6600_NR_STATUS_LINES,
+ ddata->status_gpios->desc,
+ values);
+ if (error)
+ return;
+
+ for (i = 0; i < PHY_MDM6600_NR_STATUS_LINES; i++) {
+ val |= values[i] << i;
+ dev_dbg(ddata->dev, "XXX %s: i: %i values[i]: %i val: %i\n",
+ __func__, i, values[i], val);
+ }
+ ddata->status = val;
+
+ dev_info(dev, "modem status: %i %s\n",
+ ddata->status,
+ phy_mdm6600_status_name[ddata->status & 7]);
+ complete(&ddata->ack);
+}
+
+static irqreturn_t phy_mdm6600_irq_thread(int irq, void *data)
+{
+ struct phy_mdm6600 *ddata = data;
+
+ schedule_delayed_work(&ddata->status_work, msecs_to_jiffies(10));
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * phy_mdm6600_wakeirq_thread - handle mode1 line OOB wake after booting
+ * @irq: interrupt
+ * @data: interrupt handler data
+ *
+ * GPIO mode1 is used initially as output to configure the USB boot
+ * mode for mdm6600. After booting it is used as input for OOB wake
+ * signal from mdm6600 to the SoC. Just use it for debug info only
+ * for now.
+ */
+static irqreturn_t phy_mdm6600_wakeirq_thread(int irq, void *data)
+{
+ struct phy_mdm6600 *ddata = data;
+ struct gpio_desc *mode_gpio1;
+ int error, wakeup;
+
+ mode_gpio1 = ddata->mode_gpios->desc[PHY_MDM6600_MODE1];
+ wakeup = gpiod_get_value(mode_gpio1);
+ if (!wakeup)
+ return IRQ_NONE;
+
+ dev_dbg(ddata->dev, "OOB wake on mode_gpio1: %i\n", wakeup);
+ error = pm_runtime_get_sync(ddata->dev);
+ if (error < 0) {
+ pm_runtime_put_noidle(ddata->dev);
+
+ return IRQ_NONE;
+ }
+
+ /* Just wake-up and kick the autosuspend timer */
+ pm_runtime_mark_last_busy(ddata->dev);
+ pm_runtime_put_autosuspend(ddata->dev);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * phy_mdm6600_init_irq() - initialize mdm6600 status IRQ lines
+ * @ddata: device driver data
+ */
+static void phy_mdm6600_init_irq(struct phy_mdm6600 *ddata)
+{
+ struct device *dev = ddata->dev;
+ int i, error, irq;
+
+ for (i = PHY_MDM6600_STATUS0;
+ i <= PHY_MDM6600_STATUS2; i++) {
+ struct gpio_desc *gpio = ddata->status_gpios->desc[i];
+
+ irq = gpiod_to_irq(gpio);
+ if (irq <= 0)
+ continue;
+
+ error = devm_request_threaded_irq(dev, irq, NULL,
+ phy_mdm6600_irq_thread,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ "mdm6600",
+ ddata);
+ if (error)
+ dev_warn(dev, "no modem status irq%i: %i\n",
+ irq, error);
+ }
+}
+
+struct phy_mdm6600_map {
+ const char *name;
+ int direction;
+};
+
+static const struct phy_mdm6600_map
+phy_mdm6600_ctrl_gpio_map[PHY_MDM6600_NR_CTRL_LINES] = {
+ { "enable", GPIOD_OUT_LOW, }, /* low = phy disabled */
+ { "power", GPIOD_OUT_LOW, }, /* low = off */
+ { "reset", GPIOD_OUT_HIGH, }, /* high = reset */
+};
+
+/**
+ * phy_mdm6600_init_lines() - initialize mdm6600 GPIO lines
+ * @ddata: device driver data
+ */
+static int phy_mdm6600_init_lines(struct phy_mdm6600 *ddata)
+{
+ struct device *dev = ddata->dev;
+ int i;
+
+ /* MDM6600 control lines */
+ for (i = 0; i < ARRAY_SIZE(phy_mdm6600_ctrl_gpio_map); i++) {
+ const struct phy_mdm6600_map *map =
+ &phy_mdm6600_ctrl_gpio_map[i];
+ struct gpio_desc **gpio = &ddata->ctrl_gpios[i];
+
+ *gpio = devm_gpiod_get(dev, map->name, map->direction);
+ if (IS_ERR(*gpio)) {
+ dev_info(dev, "gpio %s error %li\n",
+ map->name, PTR_ERR(*gpio));
+ return PTR_ERR(*gpio);
+ }
+ }
+
+ /* MDM6600 USB start-up mode output lines */
+ ddata->mode_gpios = devm_gpiod_get_array(dev, "motorola,mode",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(ddata->mode_gpios))
+ return PTR_ERR(ddata->mode_gpios);
+
+ if (ddata->mode_gpios->ndescs != PHY_MDM6600_NR_MODE_LINES)
+ return -EINVAL;
+
+ /* MDM6600 status input lines */
+ ddata->status_gpios = devm_gpiod_get_array(dev, "motorola,status",
+ GPIOD_IN);
+ if (IS_ERR(ddata->status_gpios))
+ return PTR_ERR(ddata->status_gpios);
+
+ if (ddata->status_gpios->ndescs != PHY_MDM6600_NR_STATUS_LINES)
+ return -EINVAL;
+
+ /* MDM6600 cmd output lines */
+ ddata->cmd_gpios = devm_gpiod_get_array(dev, "motorola,cmd",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(ddata->cmd_gpios))
+ return PTR_ERR(ddata->cmd_gpios);
+
+ if (ddata->cmd_gpios->ndescs != PHY_MDM6600_NR_CMD_LINES)
+ return -EINVAL;
+
+ return 0;
+}
+
+/**
+ * phy_mdm6600_device_power_on() - power on mdm6600 device
+ * @ddata: device driver data
+ *
+ * To get the integrated USB phy in MDM6600 takes some hoops. We must ensure
+ * the shared USB bootmode GPIOs are configured, then request modem start-up,
+ * reset and power-up.. And then we need to recycle the shared USB bootmode
+ * GPIOs as they are also used for Out of Band (OOB) wake for the USB and
+ * TS 27.010 serial mux.
+ */
+static int phy_mdm6600_device_power_on(struct phy_mdm6600 *ddata)
+{
+ struct gpio_desc *mode_gpio0, *mode_gpio1, *reset_gpio, *power_gpio;
+ int error = 0, wakeirq;
+
+ mode_gpio0 = ddata->mode_gpios->desc[PHY_MDM6600_MODE0];
+ mode_gpio1 = ddata->mode_gpios->desc[PHY_MDM6600_MODE1];
+ reset_gpio = ddata->ctrl_gpios[PHY_MDM6600_RESET];
+ power_gpio = ddata->ctrl_gpios[PHY_MDM6600_POWER];
+
+ /*
+ * Shared GPIOs must be low for normal USB mode. After booting
+ * they are used for OOB wake signaling. These can be also used
+ * to configure USB flashing mode later on based on a module
+ * parameter.
+ */
+ gpiod_set_value_cansleep(mode_gpio0, 0);
+ gpiod_set_value_cansleep(mode_gpio1, 0);
+
+ /* Request start-up mode */
+ phy_mdm6600_cmd(ddata, PHY_MDM6600_CMD_NO_BYPASS);
+
+ /* Request a reset first */
+ gpiod_set_value_cansleep(reset_gpio, 0);
+ msleep(100);
+
+ /* Toggle power GPIO to request mdm6600 to start */
+ gpiod_set_value_cansleep(power_gpio, 1);
+ msleep(100);
+ gpiod_set_value_cansleep(power_gpio, 0);
+
+ /*
+ * Looks like the USB PHY needs between 2.2 to 4 seconds.
+ * If we try to use it before that, we will get L3 errors
+ * from omap-usb-host trying to access the PHY. See also
+ * phy_mdm6600_init() for -EPROBE_DEFER.
+ */
+ msleep(PHY_MDM6600_PHY_DELAY_MS);
+ ddata->enabled = true;
+
+ /* Booting up the rest of MDM6600 will take total about 8 seconds */
+ dev_info(ddata->dev, "Waiting for power up request to complete..\n");
+ if (wait_for_completion_timeout(&ddata->ack,
+ msecs_to_jiffies(PHY_MDM6600_ENABLED_DELAY_MS))) {
+ if (ddata->status > PHY_MDM6600_STATUS_PANIC &&
+ ddata->status < PHY_MDM6600_STATUS_SHUTDOWN_ACK)
+ dev_info(ddata->dev, "Powered up OK\n");
+ } else {
+ ddata->enabled = false;
+ error = -ETIMEDOUT;
+ dev_err(ddata->dev, "Timed out powering up\n");
+ }
+
+ /* Reconfigure mode1 GPIO as input for OOB wake */
+ gpiod_direction_input(mode_gpio1);
+
+ wakeirq = gpiod_to_irq(mode_gpio1);
+ if (wakeirq <= 0)
+ return wakeirq;
+
+ error = devm_request_threaded_irq(ddata->dev, wakeirq, NULL,
+ phy_mdm6600_wakeirq_thread,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ "mdm6600-wake",
+ ddata);
+ if (error)
+ dev_warn(ddata->dev, "no modem wakeirq irq%i: %i\n",
+ wakeirq, error);
+
+ ddata->running = true;
+
+ return error;
+}
+
+/**
+ * phy_mdm6600_device_power_off() - power off mdm6600 device
+ * @ddata: device driver data
+ */
+static void phy_mdm6600_device_power_off(struct phy_mdm6600 *ddata)
+{
+ struct gpio_desc *reset_gpio =
+ ddata->ctrl_gpios[PHY_MDM6600_RESET];
+
+ ddata->enabled = false;
+ phy_mdm6600_cmd(ddata, PHY_MDM6600_CMD_BP_SHUTDOWN_REQ);
+ msleep(100);
+
+ gpiod_set_value_cansleep(reset_gpio, 1);
+
+ dev_info(ddata->dev, "Waiting for power down request to complete.. ");
+ if (wait_for_completion_timeout(&ddata->ack,
+ msecs_to_jiffies(5000))) {
+ if (ddata->status == PHY_MDM6600_STATUS_PANIC)
+ dev_info(ddata->dev, "Powered down OK\n");
+ } else {
+ dev_err(ddata->dev, "Timed out powering down\n");
+ }
+}
+
+static void phy_mdm6600_deferred_power_on(struct work_struct *work)
+{
+ struct phy_mdm6600 *ddata;
+ int error;
+
+ ddata = container_of(work, struct phy_mdm6600, bootup_work.work);
+
+ error = phy_mdm6600_device_power_on(ddata);
+ if (error)
+ dev_err(ddata->dev, "Device not functional\n");
+}
+
+/*
+ * USB suspend puts mdm6600 into low power mode. For any n_gsm using apps,
+ * we need to keep the modem awake by kicking it's mode0 GPIO. This will
+ * keep the modem awake for about 1.2 seconds. When no n_gsm apps are using
+ * the modem, runtime PM auto mode can be enabled so modem can enter low
+ * power mode.
+ */
+static void phy_mdm6600_wake_modem(struct phy_mdm6600 *ddata)
+{
+ struct gpio_desc *mode_gpio0;
+
+ mode_gpio0 = ddata->mode_gpios->desc[PHY_MDM6600_MODE0];
+ gpiod_set_value_cansleep(mode_gpio0, 1);
+ usleep_range(5, 15);
+ gpiod_set_value_cansleep(mode_gpio0, 0);
+ if (ddata->awake)
+ usleep_range(5, 15);
+ else
+ msleep(MDM6600_MODEM_WAKE_DELAY_MS);
+}
+
+static void phy_mdm6600_modem_wake(struct work_struct *work)
+{
+ struct phy_mdm6600 *ddata;
+
+ ddata = container_of(work, struct phy_mdm6600, modem_wake_work.work);
+ phy_mdm6600_wake_modem(ddata);
+
+ /*
+ * The modem does not always stay awake 1.2 seconds after toggling
+ * the wake GPIO, and sometimes it idles after about some 600 ms
+ * making writes time out.
+ */
+ schedule_delayed_work(&ddata->modem_wake_work,
+ msecs_to_jiffies(PHY_MDM6600_WAKE_KICK_MS));
+}
+
+static int __maybe_unused phy_mdm6600_runtime_suspend(struct device *dev)
+{
+ struct phy_mdm6600 *ddata = dev_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&ddata->modem_wake_work);
+ ddata->awake = false;
+
+ return 0;
+}
+
+static int __maybe_unused phy_mdm6600_runtime_resume(struct device *dev)
+{
+ struct phy_mdm6600 *ddata = dev_get_drvdata(dev);
+
+ phy_mdm6600_modem_wake(&ddata->modem_wake_work.work);
+ ddata->awake = true;
+
+ return 0;
+}
+
+static const struct dev_pm_ops phy_mdm6600_pm_ops = {
+ SET_RUNTIME_PM_OPS(phy_mdm6600_runtime_suspend,
+ phy_mdm6600_runtime_resume, NULL)
+};
+
+static const struct of_device_id phy_mdm6600_id_table[] = {
+ { .compatible = "motorola,mapphone-mdm6600", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, phy_mdm6600_id_table);
+
+static int phy_mdm6600_probe(struct platform_device *pdev)
+{
+ struct phy_mdm6600 *ddata;
+ int error;
+
+ ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ INIT_DELAYED_WORK(&ddata->bootup_work,
+ phy_mdm6600_deferred_power_on);
+ INIT_DELAYED_WORK(&ddata->status_work, phy_mdm6600_status);
+ INIT_DELAYED_WORK(&ddata->modem_wake_work, phy_mdm6600_modem_wake);
+ init_completion(&ddata->ack);
+
+ ddata->dev = &pdev->dev;
+ platform_set_drvdata(pdev, ddata);
+
+ error = phy_mdm6600_init_lines(ddata);
+ if (error)
+ return error;
+
+ phy_mdm6600_init_irq(ddata);
+
+ ddata->generic_phy = devm_phy_create(ddata->dev, NULL, &gpio_usb_ops);
+ if (IS_ERR(ddata->generic_phy)) {
+ error = PTR_ERR(ddata->generic_phy);
+ goto cleanup;
+ }
+
+ phy_set_drvdata(ddata->generic_phy, ddata);
+
+ ddata->phy_provider =
+ devm_of_phy_provider_register(ddata->dev,
+ of_phy_simple_xlate);
+ if (IS_ERR(ddata->phy_provider)) {
+ error = PTR_ERR(ddata->phy_provider);
+ goto cleanup;
+ }
+
+ schedule_delayed_work(&ddata->bootup_work, 0);
+
+ /*
+ * See phy_mdm6600_device_power_on(). We should be able
+ * to remove this eventually when ohci-platform can deal
+ * with -EPROBE_DEFER.
+ */
+ msleep(PHY_MDM6600_PHY_DELAY_MS + 500);
+
+ /*
+ * Enable PM runtime only after PHY has been powered up properly.
+ * It is currently only needed after USB suspends mdm6600 and n_gsm
+ * needs to access the device. We don't want to do this earlier as
+ * gpio mode0 pin doubles as mdm6600 wake-up gpio.
+ */
+ pm_runtime_use_autosuspend(ddata->dev);
+ pm_runtime_set_autosuspend_delay(ddata->dev,
+ MDM6600_MODEM_IDLE_DELAY_MS);
+ pm_runtime_enable(ddata->dev);
+ error = pm_runtime_get_sync(ddata->dev);
+ if (error < 0) {
+ dev_warn(ddata->dev, "failed to wake modem: %i\n", error);
+ pm_runtime_put_noidle(ddata->dev);
+ }
+ pm_runtime_mark_last_busy(ddata->dev);
+ pm_runtime_put_autosuspend(ddata->dev);
+
+ return 0;
+
+cleanup:
+ phy_mdm6600_device_power_off(ddata);
+ return error;
+}
+
+static int phy_mdm6600_remove(struct platform_device *pdev)
+{
+ struct phy_mdm6600 *ddata = platform_get_drvdata(pdev);
+ struct gpio_desc *reset_gpio = ddata->ctrl_gpios[PHY_MDM6600_RESET];
+
+ pm_runtime_dont_use_autosuspend(ddata->dev);
+ pm_runtime_put_sync(ddata->dev);
+ pm_runtime_disable(ddata->dev);
+
+ if (!ddata->running)
+ wait_for_completion_timeout(&ddata->ack,
+ msecs_to_jiffies(PHY_MDM6600_ENABLED_DELAY_MS));
+
+ gpiod_set_value_cansleep(reset_gpio, 1);
+ phy_mdm6600_device_power_off(ddata);
+
+ cancel_delayed_work_sync(&ddata->modem_wake_work);
+ cancel_delayed_work_sync(&ddata->bootup_work);
+ cancel_delayed_work_sync(&ddata->status_work);
+
+ return 0;
+}
+
+static struct platform_driver phy_mdm6600_driver = {
+ .probe = phy_mdm6600_probe,
+ .remove = phy_mdm6600_remove,
+ .driver = {
+ .name = "phy-mapphone-mdm6600",
+ .pm = &phy_mdm6600_pm_ops,
+ .of_match_table = of_match_ptr(phy_mdm6600_id_table),
+ },
+};
+
+module_platform_driver(phy_mdm6600_driver);
+
+MODULE_ALIAS("platform:gpio_usb");
+MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
+MODULE_DESCRIPTION("mdm6600 gpio usb phy driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c
new file mode 100644
index 000000000..35fd38c5a
--- /dev/null
+++ b/drivers/phy/phy-core.c
@@ -0,0 +1,1061 @@
+/*
+ * phy-core.c -- Generic Phy framework.
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
+ *
+ * Author: Kishon Vijay Abraham I <kishon@ti.com>
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/idr.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+
+static struct class *phy_class;
+static DEFINE_MUTEX(phy_provider_mutex);
+static LIST_HEAD(phy_provider_list);
+static LIST_HEAD(phys);
+static DEFINE_IDA(phy_ida);
+
+static void devm_phy_release(struct device *dev, void *res)
+{
+ struct phy *phy = *(struct phy **)res;
+
+ phy_put(phy);
+}
+
+static void devm_phy_provider_release(struct device *dev, void *res)
+{
+ struct phy_provider *phy_provider = *(struct phy_provider **)res;
+
+ of_phy_provider_unregister(phy_provider);
+}
+
+static void devm_phy_consume(struct device *dev, void *res)
+{
+ struct phy *phy = *(struct phy **)res;
+
+ phy_destroy(phy);
+}
+
+static int devm_phy_match(struct device *dev, void *res, void *match_data)
+{
+ struct phy **phy = res;
+
+ return *phy == match_data;
+}
+
+/**
+ * phy_create_lookup() - allocate and register PHY/device association
+ * @phy: the phy of the association
+ * @con_id: connection ID string on device
+ * @dev_id: the device of the association
+ *
+ * Creates and registers phy_lookup entry.
+ */
+int phy_create_lookup(struct phy *phy, const char *con_id, const char *dev_id)
+{
+ struct phy_lookup *pl;
+
+ if (!phy || !dev_id || !con_id)
+ return -EINVAL;
+
+ pl = kzalloc(sizeof(*pl), GFP_KERNEL);
+ if (!pl)
+ return -ENOMEM;
+
+ pl->dev_id = dev_id;
+ pl->con_id = con_id;
+ pl->phy = phy;
+
+ mutex_lock(&phy_provider_mutex);
+ list_add_tail(&pl->node, &phys);
+ mutex_unlock(&phy_provider_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(phy_create_lookup);
+
+/**
+ * phy_remove_lookup() - find and remove PHY/device association
+ * @phy: the phy of the association
+ * @con_id: connection ID string on device
+ * @dev_id: the device of the association
+ *
+ * Finds and unregisters phy_lookup entry that was created with
+ * phy_create_lookup().
+ */
+void phy_remove_lookup(struct phy *phy, const char *con_id, const char *dev_id)
+{
+ struct phy_lookup *pl;
+
+ if (!phy || !dev_id || !con_id)
+ return;
+
+ mutex_lock(&phy_provider_mutex);
+ list_for_each_entry(pl, &phys, node)
+ if (pl->phy == phy && !strcmp(pl->dev_id, dev_id) &&
+ !strcmp(pl->con_id, con_id)) {
+ list_del(&pl->node);
+ kfree(pl);
+ break;
+ }
+ mutex_unlock(&phy_provider_mutex);
+}
+EXPORT_SYMBOL_GPL(phy_remove_lookup);
+
+static struct phy *phy_find(struct device *dev, const char *con_id)
+{
+ const char *dev_id = dev_name(dev);
+ struct phy_lookup *p, *pl = NULL;
+
+ mutex_lock(&phy_provider_mutex);
+ list_for_each_entry(p, &phys, node)
+ if (!strcmp(p->dev_id, dev_id) && !strcmp(p->con_id, con_id)) {
+ pl = p;
+ break;
+ }
+ mutex_unlock(&phy_provider_mutex);
+
+ return pl ? pl->phy : ERR_PTR(-ENODEV);
+}
+
+static struct phy_provider *of_phy_provider_lookup(struct device_node *node)
+{
+ struct phy_provider *phy_provider;
+ struct device_node *child;
+
+ list_for_each_entry(phy_provider, &phy_provider_list, list) {
+ if (phy_provider->dev->of_node == node)
+ return phy_provider;
+
+ for_each_child_of_node(phy_provider->children, child)
+ if (child == node)
+ return phy_provider;
+ }
+
+ return ERR_PTR(-EPROBE_DEFER);
+}
+
+int phy_pm_runtime_get(struct phy *phy)
+{
+ int ret;
+
+ if (!phy)
+ return 0;
+
+ if (!pm_runtime_enabled(&phy->dev))
+ return -ENOTSUPP;
+
+ ret = pm_runtime_get(&phy->dev);
+ if (ret < 0 && ret != -EINPROGRESS)
+ pm_runtime_put_noidle(&phy->dev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phy_pm_runtime_get);
+
+int phy_pm_runtime_get_sync(struct phy *phy)
+{
+ int ret;
+
+ if (!phy)
+ return 0;
+
+ if (!pm_runtime_enabled(&phy->dev))
+ return -ENOTSUPP;
+
+ ret = pm_runtime_get_sync(&phy->dev);
+ if (ret < 0)
+ pm_runtime_put_sync(&phy->dev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phy_pm_runtime_get_sync);
+
+int phy_pm_runtime_put(struct phy *phy)
+{
+ if (!phy)
+ return 0;
+
+ if (!pm_runtime_enabled(&phy->dev))
+ return -ENOTSUPP;
+
+ return pm_runtime_put(&phy->dev);
+}
+EXPORT_SYMBOL_GPL(phy_pm_runtime_put);
+
+int phy_pm_runtime_put_sync(struct phy *phy)
+{
+ if (!phy)
+ return 0;
+
+ if (!pm_runtime_enabled(&phy->dev))
+ return -ENOTSUPP;
+
+ return pm_runtime_put_sync(&phy->dev);
+}
+EXPORT_SYMBOL_GPL(phy_pm_runtime_put_sync);
+
+void phy_pm_runtime_allow(struct phy *phy)
+{
+ if (!phy)
+ return;
+
+ if (!pm_runtime_enabled(&phy->dev))
+ return;
+
+ pm_runtime_allow(&phy->dev);
+}
+EXPORT_SYMBOL_GPL(phy_pm_runtime_allow);
+
+void phy_pm_runtime_forbid(struct phy *phy)
+{
+ if (!phy)
+ return;
+
+ if (!pm_runtime_enabled(&phy->dev))
+ return;
+
+ pm_runtime_forbid(&phy->dev);
+}
+EXPORT_SYMBOL_GPL(phy_pm_runtime_forbid);
+
+int phy_init(struct phy *phy)
+{
+ int ret;
+
+ if (!phy)
+ return 0;
+
+ ret = phy_pm_runtime_get_sync(phy);
+ if (ret < 0 && ret != -ENOTSUPP)
+ return ret;
+ ret = 0; /* Override possible ret == -ENOTSUPP */
+
+ mutex_lock(&phy->mutex);
+ if (phy->init_count == 0 && phy->ops->init) {
+ ret = phy->ops->init(phy);
+ if (ret < 0) {
+ dev_err(&phy->dev, "phy init failed --> %d\n", ret);
+ goto out;
+ }
+ }
+ ++phy->init_count;
+
+out:
+ mutex_unlock(&phy->mutex);
+ phy_pm_runtime_put(phy);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phy_init);
+
+int phy_exit(struct phy *phy)
+{
+ int ret;
+
+ if (!phy)
+ return 0;
+
+ ret = phy_pm_runtime_get_sync(phy);
+ if (ret < 0 && ret != -ENOTSUPP)
+ return ret;
+ ret = 0; /* Override possible ret == -ENOTSUPP */
+
+ mutex_lock(&phy->mutex);
+ if (phy->init_count == 1 && phy->ops->exit) {
+ ret = phy->ops->exit(phy);
+ if (ret < 0) {
+ dev_err(&phy->dev, "phy exit failed --> %d\n", ret);
+ goto out;
+ }
+ }
+ --phy->init_count;
+
+out:
+ mutex_unlock(&phy->mutex);
+ phy_pm_runtime_put(phy);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phy_exit);
+
+int phy_power_on(struct phy *phy)
+{
+ int ret = 0;
+
+ if (!phy)
+ goto out;
+
+ if (phy->pwr) {
+ ret = regulator_enable(phy->pwr);
+ if (ret)
+ goto out;
+ }
+
+ ret = phy_pm_runtime_get_sync(phy);
+ if (ret < 0 && ret != -ENOTSUPP)
+ goto err_pm_sync;
+
+ ret = 0; /* Override possible ret == -ENOTSUPP */
+
+ mutex_lock(&phy->mutex);
+ if (phy->power_count == 0 && phy->ops->power_on) {
+ ret = phy->ops->power_on(phy);
+ if (ret < 0) {
+ dev_err(&phy->dev, "phy poweron failed --> %d\n", ret);
+ goto err_pwr_on;
+ }
+ }
+ ++phy->power_count;
+ mutex_unlock(&phy->mutex);
+ return 0;
+
+err_pwr_on:
+ mutex_unlock(&phy->mutex);
+ phy_pm_runtime_put_sync(phy);
+err_pm_sync:
+ if (phy->pwr)
+ regulator_disable(phy->pwr);
+out:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phy_power_on);
+
+int phy_power_off(struct phy *phy)
+{
+ int ret;
+
+ if (!phy)
+ return 0;
+
+ mutex_lock(&phy->mutex);
+ if (phy->power_count == 1 && phy->ops->power_off) {
+ ret = phy->ops->power_off(phy);
+ if (ret < 0) {
+ dev_err(&phy->dev, "phy poweroff failed --> %d\n", ret);
+ mutex_unlock(&phy->mutex);
+ return ret;
+ }
+ }
+ --phy->power_count;
+ mutex_unlock(&phy->mutex);
+ phy_pm_runtime_put(phy);
+
+ if (phy->pwr)
+ regulator_disable(phy->pwr);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(phy_power_off);
+
+int phy_set_mode(struct phy *phy, enum phy_mode mode)
+{
+ int ret;
+
+ if (!phy || !phy->ops->set_mode)
+ return 0;
+
+ mutex_lock(&phy->mutex);
+ ret = phy->ops->set_mode(phy, mode);
+ if (!ret)
+ phy->attrs.mode = mode;
+ mutex_unlock(&phy->mutex);
+
+ return ret;
+}
+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);
+
+int phy_calibrate(struct phy *phy)
+{
+ int ret;
+
+ if (!phy || !phy->ops->calibrate)
+ return 0;
+
+ mutex_lock(&phy->mutex);
+ ret = phy->ops->calibrate(phy);
+ mutex_unlock(&phy->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phy_calibrate);
+
+/**
+ * _of_phy_get() - lookup and obtain a reference to a phy by phandle
+ * @np: device_node for which to get the phy
+ * @index: the index of the phy
+ *
+ * Returns the phy associated with the given phandle value,
+ * after getting a refcount to it or -ENODEV if there is no such phy or
+ * -EPROBE_DEFER if there is a phandle to the phy, but the device is
+ * not yet loaded. This function uses of_xlate call back function provided
+ * while registering the phy_provider to find the phy instance.
+ */
+static struct phy *_of_phy_get(struct device_node *np, int index)
+{
+ int ret;
+ struct phy_provider *phy_provider;
+ struct phy *phy = NULL;
+ struct of_phandle_args args;
+
+ ret = of_parse_phandle_with_args(np, "phys", "#phy-cells",
+ index, &args);
+ if (ret)
+ return ERR_PTR(-ENODEV);
+
+ /* This phy type handled by the usb-phy subsystem for now */
+ if (of_device_is_compatible(args.np, "usb-nop-xceiv"))
+ return ERR_PTR(-ENODEV);
+
+ mutex_lock(&phy_provider_mutex);
+ phy_provider = of_phy_provider_lookup(args.np);
+ if (IS_ERR(phy_provider) || !try_module_get(phy_provider->owner)) {
+ phy = ERR_PTR(-EPROBE_DEFER);
+ goto out_unlock;
+ }
+
+ if (!of_device_is_available(args.np)) {
+ dev_warn(phy_provider->dev, "Requested PHY is disabled\n");
+ phy = ERR_PTR(-ENODEV);
+ goto out_put_module;
+ }
+
+ phy = phy_provider->of_xlate(phy_provider->dev, &args);
+
+out_put_module:
+ module_put(phy_provider->owner);
+
+out_unlock:
+ mutex_unlock(&phy_provider_mutex);
+ of_node_put(args.np);
+
+ return phy;
+}
+
+/**
+ * of_phy_get() - lookup and obtain a reference to a phy using a device_node.
+ * @np: device_node for which to get the phy
+ * @con_id: name of the phy from device's point of view
+ *
+ * Returns the phy driver, after getting a refcount to it; or
+ * -ENODEV if there is no such phy. The caller is responsible for
+ * calling phy_put() to release that count.
+ */
+struct phy *of_phy_get(struct device_node *np, const char *con_id)
+{
+ struct phy *phy = NULL;
+ int index = 0;
+
+ if (con_id)
+ index = of_property_match_string(np, "phy-names", con_id);
+
+ phy = _of_phy_get(np, index);
+ if (IS_ERR(phy))
+ return phy;
+
+ if (!try_module_get(phy->ops->owner))
+ return ERR_PTR(-EPROBE_DEFER);
+
+ get_device(&phy->dev);
+
+ return phy;
+}
+EXPORT_SYMBOL_GPL(of_phy_get);
+
+/**
+ * phy_put() - release the PHY
+ * @phy: the phy returned by phy_get()
+ *
+ * Releases a refcount the caller received from phy_get().
+ */
+void phy_put(struct phy *phy)
+{
+ if (!phy || IS_ERR(phy))
+ return;
+
+ module_put(phy->ops->owner);
+ put_device(&phy->dev);
+}
+EXPORT_SYMBOL_GPL(phy_put);
+
+/**
+ * devm_phy_put() - release the PHY
+ * @dev: device that wants to release this phy
+ * @phy: the phy returned by devm_phy_get()
+ *
+ * destroys the devres associated with this phy and invokes phy_put
+ * to release the phy.
+ */
+void devm_phy_put(struct device *dev, struct phy *phy)
+{
+ int r;
+
+ if (!phy)
+ return;
+
+ r = devres_destroy(dev, devm_phy_release, devm_phy_match, phy);
+ dev_WARN_ONCE(dev, r, "couldn't find PHY resource\n");
+}
+EXPORT_SYMBOL_GPL(devm_phy_put);
+
+/**
+ * of_phy_simple_xlate() - returns the phy instance from phy provider
+ * @dev: the PHY provider device
+ * @args: of_phandle_args (not used here)
+ *
+ * Intended to be used by phy provider for the common case where #phy-cells is
+ * 0. For other cases where #phy-cells is greater than '0', the phy provider
+ * should provide a custom of_xlate function that reads the *args* and returns
+ * the appropriate phy.
+ */
+struct phy *of_phy_simple_xlate(struct device *dev, struct of_phandle_args
+ *args)
+{
+ struct phy *phy;
+ struct class_dev_iter iter;
+
+ class_dev_iter_init(&iter, phy_class, NULL, NULL);
+ while ((dev = class_dev_iter_next(&iter))) {
+ phy = to_phy(dev);
+ if (args->np != phy->dev.of_node)
+ continue;
+
+ class_dev_iter_exit(&iter);
+ return phy;
+ }
+
+ class_dev_iter_exit(&iter);
+ return ERR_PTR(-ENODEV);
+}
+EXPORT_SYMBOL_GPL(of_phy_simple_xlate);
+
+/**
+ * phy_get() - lookup and obtain a reference to a phy.
+ * @dev: device that requests this phy
+ * @string: the phy name as given in the dt data or the name of the controller
+ * port for non-dt case
+ *
+ * Returns the phy driver, after getting a refcount to it; or
+ * -ENODEV if there is no such phy. The caller is responsible for
+ * calling phy_put() to release that count.
+ */
+struct phy *phy_get(struct device *dev, const char *string)
+{
+ int index = 0;
+ struct phy *phy;
+
+ if (string == NULL) {
+ dev_WARN(dev, "missing string\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (dev->of_node) {
+ index = of_property_match_string(dev->of_node, "phy-names",
+ string);
+ phy = _of_phy_get(dev->of_node, index);
+ } else {
+ phy = phy_find(dev, string);
+ }
+ if (IS_ERR(phy))
+ return phy;
+
+ if (!try_module_get(phy->ops->owner))
+ return ERR_PTR(-EPROBE_DEFER);
+
+ get_device(&phy->dev);
+
+ return phy;
+}
+EXPORT_SYMBOL_GPL(phy_get);
+
+/**
+ * phy_optional_get() - lookup and obtain a reference to an optional phy.
+ * @dev: device that requests this phy
+ * @string: the phy name as given in the dt data or the name of the controller
+ * port for non-dt case
+ *
+ * Returns the phy driver, after getting a refcount to it; or
+ * NULL if there is no such phy. The caller is responsible for
+ * calling phy_put() to release that count.
+ */
+struct phy *phy_optional_get(struct device *dev, const char *string)
+{
+ struct phy *phy = phy_get(dev, string);
+
+ if (IS_ERR(phy) && (PTR_ERR(phy) == -ENODEV))
+ phy = NULL;
+
+ return phy;
+}
+EXPORT_SYMBOL_GPL(phy_optional_get);
+
+/**
+ * devm_phy_get() - lookup and obtain a reference to a phy.
+ * @dev: device that requests this phy
+ * @string: the phy name as given in the dt data or phy device name
+ * for non-dt case
+ *
+ * Gets the phy using phy_get(), and associates a device with it using
+ * devres. On driver detach, release function is invoked on the devres data,
+ * then, devres data is freed.
+ */
+struct phy *devm_phy_get(struct device *dev, const char *string)
+{
+ struct phy **ptr, *phy;
+
+ ptr = devres_alloc(devm_phy_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ phy = phy_get(dev, string);
+ if (!IS_ERR(phy)) {
+ *ptr = phy;
+ devres_add(dev, ptr);
+ } else {
+ devres_free(ptr);
+ }
+
+ return phy;
+}
+EXPORT_SYMBOL_GPL(devm_phy_get);
+
+/**
+ * devm_phy_optional_get() - lookup and obtain a reference to an optional phy.
+ * @dev: device that requests this phy
+ * @string: the phy name as given in the dt data or phy device name
+ * for non-dt case
+ *
+ * Gets the phy using phy_get(), and associates a device with it using
+ * devres. On driver detach, release function is invoked on the devres
+ * data, then, devres data is freed. This differs to devm_phy_get() in
+ * that if the phy does not exist, it is not considered an error and
+ * -ENODEV will not be returned. Instead the NULL phy is returned,
+ * which can be passed to all other phy consumer calls.
+ */
+struct phy *devm_phy_optional_get(struct device *dev, const char *string)
+{
+ struct phy *phy = devm_phy_get(dev, string);
+
+ if (IS_ERR(phy) && (PTR_ERR(phy) == -ENODEV))
+ phy = NULL;
+
+ return phy;
+}
+EXPORT_SYMBOL_GPL(devm_phy_optional_get);
+
+/**
+ * devm_of_phy_get() - lookup and obtain a reference to a phy.
+ * @dev: device that requests this phy
+ * @np: node containing the phy
+ * @con_id: name of the phy from device's point of view
+ *
+ * Gets the phy using of_phy_get(), and associates a device with it using
+ * devres. On driver detach, release function is invoked on the devres data,
+ * then, devres data is freed.
+ */
+struct phy *devm_of_phy_get(struct device *dev, struct device_node *np,
+ const char *con_id)
+{
+ struct phy **ptr, *phy;
+
+ ptr = devres_alloc(devm_phy_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ phy = of_phy_get(np, con_id);
+ if (!IS_ERR(phy)) {
+ *ptr = phy;
+ devres_add(dev, ptr);
+ } else {
+ devres_free(ptr);
+ }
+
+ return phy;
+}
+EXPORT_SYMBOL_GPL(devm_of_phy_get);
+
+/**
+ * devm_of_phy_get_by_index() - lookup and obtain a reference to a phy by index.
+ * @dev: device that requests this phy
+ * @np: node containing the phy
+ * @index: index of the phy
+ *
+ * Gets the phy using _of_phy_get(), then gets a refcount to it,
+ * and associates a device with it using devres. On driver detach,
+ * release function is invoked on the devres data,
+ * then, devres data is freed.
+ *
+ */
+struct phy *devm_of_phy_get_by_index(struct device *dev, struct device_node *np,
+ int index)
+{
+ struct phy **ptr, *phy;
+
+ ptr = devres_alloc(devm_phy_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ phy = _of_phy_get(np, index);
+ if (IS_ERR(phy)) {
+ devres_free(ptr);
+ return phy;
+ }
+
+ if (!try_module_get(phy->ops->owner)) {
+ devres_free(ptr);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ get_device(&phy->dev);
+
+ *ptr = phy;
+ devres_add(dev, ptr);
+
+ return phy;
+}
+EXPORT_SYMBOL_GPL(devm_of_phy_get_by_index);
+
+/**
+ * phy_create() - create a new phy
+ * @dev: device that is creating the new phy
+ * @node: device node of the phy
+ * @ops: function pointers for performing phy operations
+ *
+ * Called to create a phy using phy framework.
+ */
+struct phy *phy_create(struct device *dev, struct device_node *node,
+ const struct phy_ops *ops)
+{
+ int ret;
+ int id;
+ struct phy *phy;
+
+ if (WARN_ON(!dev))
+ return ERR_PTR(-EINVAL);
+
+ phy = kzalloc(sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return ERR_PTR(-ENOMEM);
+
+ id = ida_simple_get(&phy_ida, 0, 0, GFP_KERNEL);
+ if (id < 0) {
+ dev_err(dev, "unable to get id\n");
+ ret = id;
+ goto free_phy;
+ }
+
+ device_initialize(&phy->dev);
+ mutex_init(&phy->mutex);
+
+ phy->dev.class = phy_class;
+ phy->dev.parent = dev;
+ phy->dev.of_node = node ?: dev->of_node;
+ phy->id = id;
+ phy->ops = ops;
+
+ ret = dev_set_name(&phy->dev, "phy-%s.%d", dev_name(dev), id);
+ if (ret)
+ goto put_dev;
+
+ /* phy-supply */
+ phy->pwr = regulator_get_optional(&phy->dev, "phy");
+ if (IS_ERR(phy->pwr)) {
+ ret = PTR_ERR(phy->pwr);
+ if (ret == -EPROBE_DEFER)
+ goto put_dev;
+
+ phy->pwr = NULL;
+ }
+
+ ret = device_add(&phy->dev);
+ if (ret)
+ goto put_dev;
+
+ if (pm_runtime_enabled(dev)) {
+ pm_runtime_enable(&phy->dev);
+ pm_runtime_no_callbacks(&phy->dev);
+ }
+
+ return phy;
+
+put_dev:
+ put_device(&phy->dev); /* calls phy_release() which frees resources */
+ return ERR_PTR(ret);
+
+free_phy:
+ kfree(phy);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(phy_create);
+
+/**
+ * devm_phy_create() - create a new phy
+ * @dev: device that is creating the new phy
+ * @node: device node of the phy
+ * @ops: function pointers for performing phy operations
+ *
+ * Creates a new PHY device adding it to the PHY class.
+ * While at that, it also associates the device with the phy using devres.
+ * On driver detach, release function is invoked on the devres data,
+ * then, devres data is freed.
+ */
+struct phy *devm_phy_create(struct device *dev, struct device_node *node,
+ const struct phy_ops *ops)
+{
+ struct phy **ptr, *phy;
+
+ ptr = devres_alloc(devm_phy_consume, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ phy = phy_create(dev, node, ops);
+ if (!IS_ERR(phy)) {
+ *ptr = phy;
+ devres_add(dev, ptr);
+ } else {
+ devres_free(ptr);
+ }
+
+ return phy;
+}
+EXPORT_SYMBOL_GPL(devm_phy_create);
+
+/**
+ * phy_destroy() - destroy the phy
+ * @phy: the phy to be destroyed
+ *
+ * Called to destroy the phy.
+ */
+void phy_destroy(struct phy *phy)
+{
+ pm_runtime_disable(&phy->dev);
+ device_unregister(&phy->dev);
+}
+EXPORT_SYMBOL_GPL(phy_destroy);
+
+/**
+ * devm_phy_destroy() - destroy the PHY
+ * @dev: device that wants to release this phy
+ * @phy: the phy returned by devm_phy_get()
+ *
+ * destroys the devres associated with this phy and invokes phy_destroy
+ * to destroy the phy.
+ */
+void devm_phy_destroy(struct device *dev, struct phy *phy)
+{
+ int r;
+
+ r = devres_destroy(dev, devm_phy_consume, devm_phy_match, phy);
+ dev_WARN_ONCE(dev, r, "couldn't find PHY resource\n");
+}
+EXPORT_SYMBOL_GPL(devm_phy_destroy);
+
+/**
+ * __of_phy_provider_register() - create/register phy provider with the framework
+ * @dev: struct device of the phy provider
+ * @children: device node containing children (if different from dev->of_node)
+ * @owner: the module owner containing of_xlate
+ * @of_xlate: function pointer to obtain phy instance from phy provider
+ *
+ * Creates struct phy_provider from dev and of_xlate function pointer.
+ * This is used in the case of dt boot for finding the phy instance from
+ * phy provider.
+ *
+ * If the PHY provider doesn't nest children directly but uses a separate
+ * child node to contain the individual children, the @children parameter
+ * can be used to override the default. If NULL, the default (dev->of_node)
+ * will be used. If non-NULL, the device node must be a child (or further
+ * descendant) of dev->of_node. Otherwise an ERR_PTR()-encoded -EINVAL
+ * error code is returned.
+ */
+struct phy_provider *__of_phy_provider_register(struct device *dev,
+ struct device_node *children, struct module *owner,
+ struct phy * (*of_xlate)(struct device *dev,
+ struct of_phandle_args *args))
+{
+ struct phy_provider *phy_provider;
+
+ /*
+ * If specified, the device node containing the children must itself
+ * be the provider's device node or a child (or further descendant)
+ * thereof.
+ */
+ if (children) {
+ struct device_node *parent = of_node_get(children), *next;
+
+ while (parent) {
+ if (parent == dev->of_node)
+ break;
+
+ next = of_get_parent(parent);
+ of_node_put(parent);
+ parent = next;
+ }
+
+ if (!parent)
+ return ERR_PTR(-EINVAL);
+
+ of_node_put(parent);
+ } else {
+ children = dev->of_node;
+ }
+
+ phy_provider = kzalloc(sizeof(*phy_provider), GFP_KERNEL);
+ if (!phy_provider)
+ return ERR_PTR(-ENOMEM);
+
+ phy_provider->dev = dev;
+ phy_provider->children = of_node_get(children);
+ phy_provider->owner = owner;
+ phy_provider->of_xlate = of_xlate;
+
+ mutex_lock(&phy_provider_mutex);
+ list_add_tail(&phy_provider->list, &phy_provider_list);
+ mutex_unlock(&phy_provider_mutex);
+
+ return phy_provider;
+}
+EXPORT_SYMBOL_GPL(__of_phy_provider_register);
+
+/**
+ * __devm_of_phy_provider_register() - create/register phy provider with the
+ * framework
+ * @dev: struct device of the phy provider
+ * @owner: the module owner containing of_xlate
+ * @of_xlate: function pointer to obtain phy instance from phy provider
+ *
+ * Creates struct phy_provider from dev and of_xlate function pointer.
+ * This is used in the case of dt boot for finding the phy instance from
+ * phy provider. While at that, it also associates the device with the
+ * phy provider using devres. On driver detach, release function is invoked
+ * on the devres data, then, devres data is freed.
+ */
+struct phy_provider *__devm_of_phy_provider_register(struct device *dev,
+ struct device_node *children, struct module *owner,
+ struct phy * (*of_xlate)(struct device *dev,
+ struct of_phandle_args *args))
+{
+ struct phy_provider **ptr, *phy_provider;
+
+ ptr = devres_alloc(devm_phy_provider_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ phy_provider = __of_phy_provider_register(dev, children, owner,
+ of_xlate);
+ if (!IS_ERR(phy_provider)) {
+ *ptr = phy_provider;
+ devres_add(dev, ptr);
+ } else {
+ devres_free(ptr);
+ }
+
+ return phy_provider;
+}
+EXPORT_SYMBOL_GPL(__devm_of_phy_provider_register);
+
+/**
+ * of_phy_provider_unregister() - unregister phy provider from the framework
+ * @phy_provider: phy provider returned by of_phy_provider_register()
+ *
+ * Removes the phy_provider created using of_phy_provider_register().
+ */
+void of_phy_provider_unregister(struct phy_provider *phy_provider)
+{
+ if (IS_ERR(phy_provider))
+ return;
+
+ mutex_lock(&phy_provider_mutex);
+ list_del(&phy_provider->list);
+ of_node_put(phy_provider->children);
+ kfree(phy_provider);
+ mutex_unlock(&phy_provider_mutex);
+}
+EXPORT_SYMBOL_GPL(of_phy_provider_unregister);
+
+/**
+ * devm_of_phy_provider_unregister() - remove phy provider from the framework
+ * @dev: struct device of the phy provider
+ *
+ * destroys the devres associated with this phy provider and invokes
+ * of_phy_provider_unregister to unregister the phy provider.
+ */
+void devm_of_phy_provider_unregister(struct device *dev,
+ struct phy_provider *phy_provider) {
+ int r;
+
+ r = devres_destroy(dev, devm_phy_provider_release, devm_phy_match,
+ phy_provider);
+ dev_WARN_ONCE(dev, r, "couldn't find PHY provider device resource\n");
+}
+EXPORT_SYMBOL_GPL(devm_of_phy_provider_unregister);
+
+/**
+ * phy_release() - release the phy
+ * @dev: the dev member within phy
+ *
+ * When the last reference to the device is removed, it is called
+ * from the embedded kobject as release method.
+ */
+static void phy_release(struct device *dev)
+{
+ struct phy *phy;
+
+ phy = to_phy(dev);
+ dev_vdbg(dev, "releasing '%s'\n", dev_name(dev));
+ regulator_put(phy->pwr);
+ ida_simple_remove(&phy_ida, phy->id);
+ kfree(phy);
+}
+
+static int __init phy_core_init(void)
+{
+ phy_class = class_create(THIS_MODULE, "phy");
+ if (IS_ERR(phy_class)) {
+ pr_err("failed to create phy class --> %ld\n",
+ PTR_ERR(phy_class));
+ return PTR_ERR(phy_class);
+ }
+
+ phy_class->dev_release = phy_release;
+
+ return 0;
+}
+module_init(phy_core_init);
+
+static void __exit phy_core_exit(void)
+{
+ class_destroy(phy_class);
+}
+module_exit(phy_core_exit);
+
+MODULE_DESCRIPTION("Generic PHY Framework");
+MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/phy-lpc18xx-usb-otg.c b/drivers/phy/phy-lpc18xx-usb-otg.c
new file mode 100644
index 000000000..7de280a45
--- /dev/null
+++ b/drivers/phy/phy-lpc18xx-usb-otg.c
@@ -0,0 +1,149 @@
+/*
+ * PHY driver for NXP LPC18xx/43xx internal USB OTG PHY
+ *
+ * Copyright (C) 2015 Joachim Eastwood <manabian@gmail.com>
+ *
+ * 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/clk.h>
+#include <linux/err.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/* USB OTG PHY register offset and bit in CREG */
+#define LPC18XX_CREG_CREG0 0x004
+#define LPC18XX_CREG_CREG0_USB0PHY BIT(5)
+
+struct lpc18xx_usb_otg_phy {
+ struct phy *phy;
+ struct clk *clk;
+ struct regmap *reg;
+};
+
+static int lpc18xx_usb_otg_phy_init(struct phy *phy)
+{
+ struct lpc18xx_usb_otg_phy *lpc = phy_get_drvdata(phy);
+ int ret;
+
+ /* The PHY must be clocked at 480 MHz */
+ ret = clk_set_rate(lpc->clk, 480000000);
+ if (ret)
+ return ret;
+
+ return clk_prepare(lpc->clk);
+}
+
+static int lpc18xx_usb_otg_phy_exit(struct phy *phy)
+{
+ struct lpc18xx_usb_otg_phy *lpc = phy_get_drvdata(phy);
+
+ clk_unprepare(lpc->clk);
+
+ return 0;
+}
+
+static int lpc18xx_usb_otg_phy_power_on(struct phy *phy)
+{
+ struct lpc18xx_usb_otg_phy *lpc = phy_get_drvdata(phy);
+ int ret;
+
+ ret = clk_enable(lpc->clk);
+ if (ret)
+ return ret;
+
+ /* The bit in CREG is cleared to enable the PHY */
+ ret = regmap_update_bits(lpc->reg, LPC18XX_CREG_CREG0,
+ LPC18XX_CREG_CREG0_USB0PHY, 0);
+ if (ret) {
+ clk_disable(lpc->clk);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lpc18xx_usb_otg_phy_power_off(struct phy *phy)
+{
+ struct lpc18xx_usb_otg_phy *lpc = phy_get_drvdata(phy);
+ int ret;
+
+ ret = regmap_update_bits(lpc->reg, LPC18XX_CREG_CREG0,
+ LPC18XX_CREG_CREG0_USB0PHY,
+ LPC18XX_CREG_CREG0_USB0PHY);
+ if (ret)
+ return ret;
+
+ clk_disable(lpc->clk);
+
+ return 0;
+}
+
+static const struct phy_ops lpc18xx_usb_otg_phy_ops = {
+ .init = lpc18xx_usb_otg_phy_init,
+ .exit = lpc18xx_usb_otg_phy_exit,
+ .power_on = lpc18xx_usb_otg_phy_power_on,
+ .power_off = lpc18xx_usb_otg_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int lpc18xx_usb_otg_phy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct lpc18xx_usb_otg_phy *lpc;
+
+ lpc = devm_kzalloc(&pdev->dev, sizeof(*lpc), GFP_KERNEL);
+ if (!lpc)
+ return -ENOMEM;
+
+ lpc->reg = syscon_node_to_regmap(pdev->dev.of_node->parent);
+ if (IS_ERR(lpc->reg)) {
+ dev_err(&pdev->dev, "failed to get syscon\n");
+ return PTR_ERR(lpc->reg);
+ }
+
+ lpc->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(lpc->clk)) {
+ dev_err(&pdev->dev, "failed to get clock\n");
+ return PTR_ERR(lpc->clk);
+ }
+
+ lpc->phy = devm_phy_create(&pdev->dev, NULL, &lpc18xx_usb_otg_phy_ops);
+ if (IS_ERR(lpc->phy)) {
+ dev_err(&pdev->dev, "failed to create PHY\n");
+ return PTR_ERR(lpc->phy);
+ }
+
+ phy_set_drvdata(lpc->phy, lpc);
+
+ phy_provider = devm_of_phy_provider_register(&pdev->dev,
+ of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id lpc18xx_usb_otg_phy_match[] = {
+ { .compatible = "nxp,lpc1850-usb-otg-phy" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, lpc18xx_usb_otg_phy_match);
+
+static struct platform_driver lpc18xx_usb_otg_phy_driver = {
+ .probe = lpc18xx_usb_otg_phy_probe,
+ .driver = {
+ .name = "lpc18xx-usb-otg-phy",
+ .of_match_table = lpc18xx_usb_otg_phy_match,
+ },
+};
+module_platform_driver(lpc18xx_usb_otg_phy_driver);
+
+MODULE_AUTHOR("Joachim Eastwood <manabian@gmail.com>");
+MODULE_DESCRIPTION("NXP LPC18xx/43xx USB OTG PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/phy-pistachio-usb.c b/drivers/phy/phy-pistachio-usb.c
new file mode 100644
index 000000000..c6db35e6b
--- /dev/null
+++ b/drivers/phy/phy-pistachio-usb.c
@@ -0,0 +1,206 @@
+/*
+ * IMG Pistachio USB PHY driver
+ *
+ * Copyright (C) 2015 Google, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <dt-bindings/phy/phy-pistachio-usb.h>
+
+#define USB_PHY_CONTROL1 0x04
+#define USB_PHY_CONTROL1_FSEL_SHIFT 2
+#define USB_PHY_CONTROL1_FSEL_MASK 0x7
+
+#define USB_PHY_STRAP_CONTROL 0x10
+#define USB_PHY_STRAP_CONTROL_REFCLK_SHIFT 4
+#define USB_PHY_STRAP_CONTROL_REFCLK_MASK 0x3
+
+#define USB_PHY_STATUS 0x14
+#define USB_PHY_STATUS_RX_PHY_CLK BIT(9)
+#define USB_PHY_STATUS_RX_UTMI_CLK BIT(8)
+#define USB_PHY_STATUS_VBUS_FAULT BIT(7)
+
+struct pistachio_usb_phy {
+ struct device *dev;
+ struct regmap *cr_top;
+ struct clk *phy_clk;
+ unsigned int refclk;
+};
+
+static const unsigned long fsel_rate_map[] = {
+ 9600000,
+ 10000000,
+ 12000000,
+ 19200000,
+ 20000000,
+ 24000000,
+ 0,
+ 50000000,
+};
+
+static int pistachio_usb_phy_power_on(struct phy *phy)
+{
+ struct pistachio_usb_phy *p_phy = phy_get_drvdata(phy);
+ unsigned long timeout, rate;
+ unsigned int i;
+ int ret;
+
+ ret = clk_prepare_enable(p_phy->phy_clk);
+ if (ret < 0) {
+ dev_err(p_phy->dev, "Failed to enable PHY clock: %d\n", ret);
+ return ret;
+ }
+
+ regmap_update_bits(p_phy->cr_top, USB_PHY_STRAP_CONTROL,
+ USB_PHY_STRAP_CONTROL_REFCLK_MASK <<
+ USB_PHY_STRAP_CONTROL_REFCLK_SHIFT,
+ p_phy->refclk << USB_PHY_STRAP_CONTROL_REFCLK_SHIFT);
+
+ rate = clk_get_rate(p_phy->phy_clk);
+ if (p_phy->refclk == REFCLK_XO_CRYSTAL && rate != 12000000) {
+ dev_err(p_phy->dev, "Unsupported rate for XO crystal: %ld\n",
+ rate);
+ ret = -EINVAL;
+ goto disable_clk;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(fsel_rate_map); i++) {
+ if (rate == fsel_rate_map[i])
+ break;
+ }
+ if (i == ARRAY_SIZE(fsel_rate_map)) {
+ dev_err(p_phy->dev, "Unsupported clock rate: %lu\n", rate);
+ ret = -EINVAL;
+ goto disable_clk;
+ }
+
+ regmap_update_bits(p_phy->cr_top, USB_PHY_CONTROL1,
+ USB_PHY_CONTROL1_FSEL_MASK <<
+ USB_PHY_CONTROL1_FSEL_SHIFT,
+ i << USB_PHY_CONTROL1_FSEL_SHIFT);
+
+ timeout = jiffies + msecs_to_jiffies(200);
+ while (time_before(jiffies, timeout)) {
+ unsigned int val;
+
+ regmap_read(p_phy->cr_top, USB_PHY_STATUS, &val);
+ if (val & USB_PHY_STATUS_VBUS_FAULT) {
+ dev_err(p_phy->dev, "VBUS fault detected\n");
+ ret = -EIO;
+ goto disable_clk;
+ }
+ if ((val & USB_PHY_STATUS_RX_PHY_CLK) &&
+ (val & USB_PHY_STATUS_RX_UTMI_CLK))
+ return 0;
+ usleep_range(1000, 1500);
+ }
+
+ dev_err(p_phy->dev, "Timed out waiting for PHY to power on\n");
+ ret = -ETIMEDOUT;
+
+disable_clk:
+ clk_disable_unprepare(p_phy->phy_clk);
+ return ret;
+}
+
+static int pistachio_usb_phy_power_off(struct phy *phy)
+{
+ struct pistachio_usb_phy *p_phy = phy_get_drvdata(phy);
+
+ clk_disable_unprepare(p_phy->phy_clk);
+
+ return 0;
+}
+
+static const struct phy_ops pistachio_usb_phy_ops = {
+ .power_on = pistachio_usb_phy_power_on,
+ .power_off = pistachio_usb_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int pistachio_usb_phy_probe(struct platform_device *pdev)
+{
+ struct pistachio_usb_phy *p_phy;
+ struct phy_provider *provider;
+ struct phy *phy;
+ int ret;
+
+ p_phy = devm_kzalloc(&pdev->dev, sizeof(*p_phy), GFP_KERNEL);
+ if (!p_phy)
+ return -ENOMEM;
+ p_phy->dev = &pdev->dev;
+ platform_set_drvdata(pdev, p_phy);
+
+ p_phy->cr_top = syscon_regmap_lookup_by_phandle(p_phy->dev->of_node,
+ "img,cr-top");
+ if (IS_ERR(p_phy->cr_top)) {
+ dev_err(p_phy->dev, "Failed to get CR_TOP registers: %ld\n",
+ PTR_ERR(p_phy->cr_top));
+ return PTR_ERR(p_phy->cr_top);
+ }
+
+ p_phy->phy_clk = devm_clk_get(p_phy->dev, "usb_phy");
+ if (IS_ERR(p_phy->phy_clk)) {
+ dev_err(p_phy->dev, "Failed to get usb_phy clock: %ld\n",
+ PTR_ERR(p_phy->phy_clk));
+ return PTR_ERR(p_phy->phy_clk);
+ }
+
+ ret = of_property_read_u32(p_phy->dev->of_node, "img,refclk",
+ &p_phy->refclk);
+ if (ret < 0) {
+ dev_err(p_phy->dev, "No reference clock selector specified\n");
+ return ret;
+ }
+
+ phy = devm_phy_create(p_phy->dev, NULL, &pistachio_usb_phy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(p_phy->dev, "Failed to create PHY: %ld\n",
+ PTR_ERR(phy));
+ return PTR_ERR(phy);
+ }
+ phy_set_drvdata(phy, p_phy);
+
+ provider = devm_of_phy_provider_register(p_phy->dev,
+ of_phy_simple_xlate);
+ if (IS_ERR(provider)) {
+ dev_err(p_phy->dev, "Failed to register PHY provider: %ld\n",
+ PTR_ERR(provider));
+ return PTR_ERR(provider);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id pistachio_usb_phy_of_match[] = {
+ { .compatible = "img,pistachio-usb-phy", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, pistachio_usb_phy_of_match);
+
+static struct platform_driver pistachio_usb_phy_driver = {
+ .probe = pistachio_usb_phy_probe,
+ .driver = {
+ .name = "pistachio-usb-phy",
+ .of_match_table = pistachio_usb_phy_of_match,
+ },
+};
+module_platform_driver(pistachio_usb_phy_driver);
+
+MODULE_AUTHOR("Andrew Bresticker <abrestic@chromium.org>");
+MODULE_DESCRIPTION("IMG Pistachio USB2.0 PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/phy-xgene.c b/drivers/phy/phy-xgene.c
new file mode 100644
index 000000000..ae266e0c8
--- /dev/null
+++ b/drivers/phy/phy-xgene.c
@@ -0,0 +1,1735 @@
+/*
+ * AppliedMicro X-Gene Multi-purpose PHY driver
+ *
+ * Copyright (c) 2014, Applied Micro Circuits Corporation
+ * Author: Loc Ho <lho@apm.com>
+ * Tuan Phan <tphan@apm.com>
+ * Suman Tripathi <stripathi@apm.com>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * The APM X-Gene PHY consists of two PLL clock macro's (CMU) and lanes.
+ * The first PLL clock macro is used for internal reference clock. The second
+ * PLL clock macro is used to generate the clock for the PHY. This driver
+ * configures the first PLL CMU, the second PLL CMU, and programs the PHY to
+ * operate according to the mode of operation. The first PLL CMU is only
+ * required if internal clock is enabled.
+ *
+ * Logical Layer Out Of HW module units:
+ *
+ * -----------------
+ * | Internal | |------|
+ * | Ref PLL CMU |----| | ------------- ---------
+ * ------------ ---- | MUX |-----|PHY PLL CMU|----| Serdes|
+ * | | | | ---------
+ * External Clock ------| | -------------
+ * |------|
+ *
+ * The Ref PLL CMU CSR (Configuration System Registers) is accessed
+ * indirectly from the SDS offset at 0x2000. It is only required for
+ * internal reference clock.
+ * The PHY PLL CMU CSR is accessed indirectly from the SDS offset at 0x0000.
+ * The Serdes CSR is accessed indirectly from the SDS offset at 0x0400.
+ *
+ * The Ref PLL CMU can be located within the same PHY IP or outside the PHY IP
+ * due to shared Ref PLL CMU. For PHY with Ref PLL CMU shared with another IP,
+ * it is located outside the PHY IP. This is the case for the PHY located
+ * at 0x1f23a000 (SATA Port 4/5). For such PHY, another resource is required
+ * to located the SDS/Ref PLL CMU module and its clock for that IP enabled.
+ *
+ * Currently, this driver only supports Gen3 SATA mode with external clock.
+ */
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/phy/phy.h>
+#include <linux/clk.h>
+
+/* Max 2 lanes per a PHY unit */
+#define MAX_LANE 2
+
+/* Register offset inside the PHY */
+#define SERDES_PLL_INDIRECT_OFFSET 0x0000
+#define SERDES_PLL_REF_INDIRECT_OFFSET 0x2000
+#define SERDES_INDIRECT_OFFSET 0x0400
+#define SERDES_LANE_STRIDE 0x0200
+
+/* Some default Serdes parameters */
+#define DEFAULT_SATA_TXBOOST_GAIN { 0x1e, 0x1e, 0x1e }
+#define DEFAULT_SATA_TXEYEDIRECTION { 0x0, 0x0, 0x0 }
+#define DEFAULT_SATA_TXEYETUNING { 0xa, 0xa, 0xa }
+#define DEFAULT_SATA_SPD_SEL { 0x1, 0x3, 0x7 }
+#define DEFAULT_SATA_TXAMP { 0x8, 0x8, 0x8 }
+#define DEFAULT_SATA_TXCN1 { 0x2, 0x2, 0x2 }
+#define DEFAULT_SATA_TXCN2 { 0x0, 0x0, 0x0 }
+#define DEFAULT_SATA_TXCP1 { 0xa, 0xa, 0xa }
+
+#define SATA_SPD_SEL_GEN3 0x7
+#define SATA_SPD_SEL_GEN2 0x3
+#define SATA_SPD_SEL_GEN1 0x1
+
+#define SSC_DISABLE 0
+#define SSC_ENABLE 1
+
+#define FBDIV_VAL_50M 0x77
+#define REFDIV_VAL_50M 0x1
+#define FBDIV_VAL_100M 0x3B
+#define REFDIV_VAL_100M 0x0
+
+/* SATA Clock/Reset CSR */
+#define SATACLKENREG 0x00000000
+#define SATA0_CORE_CLKEN 0x00000002
+#define SATA1_CORE_CLKEN 0x00000004
+#define SATASRESETREG 0x00000004
+#define SATA_MEM_RESET_MASK 0x00000020
+#define SATA_MEM_RESET_RD(src) (((src) & 0x00000020) >> 5)
+#define SATA_SDS_RESET_MASK 0x00000004
+#define SATA_CSR_RESET_MASK 0x00000001
+#define SATA_CORE_RESET_MASK 0x00000002
+#define SATA_PMCLK_RESET_MASK 0x00000010
+#define SATA_PCLK_RESET_MASK 0x00000008
+
+/* SDS CSR used for PHY Indirect access */
+#define SATA_ENET_SDS_PCS_CTL0 0x00000000
+#define REGSPEC_CFG_I_TX_WORDMODE0_SET(dst, src) \
+ (((dst) & ~0x00070000) | (((u32) (src) << 16) & 0x00070000))
+#define REGSPEC_CFG_I_RX_WORDMODE0_SET(dst, src) \
+ (((dst) & ~0x00e00000) | (((u32) (src) << 21) & 0x00e00000))
+#define SATA_ENET_SDS_CTL0 0x0000000c
+#define REGSPEC_CFG_I_CUSTOMER_PIN_MODE0_SET(dst, src) \
+ (((dst) & ~0x00007fff) | (((u32) (src)) & 0x00007fff))
+#define SATA_ENET_SDS_CTL1 0x00000010
+#define CFG_I_SPD_SEL_CDR_OVR1_SET(dst, src) \
+ (((dst) & ~0x0000000f) | (((u32) (src)) & 0x0000000f))
+#define SATA_ENET_SDS_RST_CTL 0x00000024
+#define SATA_ENET_SDS_IND_CMD_REG 0x0000003c
+#define CFG_IND_WR_CMD_MASK 0x00000001
+#define CFG_IND_RD_CMD_MASK 0x00000002
+#define CFG_IND_CMD_DONE_MASK 0x00000004
+#define CFG_IND_ADDR_SET(dst, src) \
+ (((dst) & ~0x003ffff0) | (((u32) (src) << 4) & 0x003ffff0))
+#define SATA_ENET_SDS_IND_RDATA_REG 0x00000040
+#define SATA_ENET_SDS_IND_WDATA_REG 0x00000044
+#define SATA_ENET_CLK_MACRO_REG 0x0000004c
+#define I_RESET_B_SET(dst, src) \
+ (((dst) & ~0x00000001) | (((u32) (src)) & 0x00000001))
+#define I_PLL_FBDIV_SET(dst, src) \
+ (((dst) & ~0x001ff000) | (((u32) (src) << 12) & 0x001ff000))
+#define I_CUSTOMEROV_SET(dst, src) \
+ (((dst) & ~0x00000f80) | (((u32) (src) << 7) & 0x00000f80))
+#define O_PLL_LOCK_RD(src) (((src) & 0x40000000) >> 30)
+#define O_PLL_READY_RD(src) (((src) & 0x80000000) >> 31)
+
+/* PLL Clock Macro Unit (CMU) CSR accessing from SDS indirectly */
+#define CMU_REG0 0x00000
+#define CMU_REG0_PLL_REF_SEL_MASK 0x00002000
+#define CMU_REG0_PLL_REF_SEL_SET(dst, src) \
+ (((dst) & ~0x00002000) | (((u32) (src) << 13) & 0x00002000))
+#define CMU_REG0_PDOWN_MASK 0x00004000
+#define CMU_REG0_CAL_COUNT_RESOL_SET(dst, src) \
+ (((dst) & ~0x000000e0) | (((u32) (src) << 5) & 0x000000e0))
+#define CMU_REG1 0x00002
+#define CMU_REG1_PLL_CP_SET(dst, src) \
+ (((dst) & ~0x00003c00) | (((u32) (src) << 10) & 0x00003c00))
+#define CMU_REG1_PLL_MANUALCAL_SET(dst, src) \
+ (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008))
+#define CMU_REG1_PLL_CP_SEL_SET(dst, src) \
+ (((dst) & ~0x000003e0) | (((u32) (src) << 5) & 0x000003e0))
+#define CMU_REG1_REFCLK_CMOS_SEL_MASK 0x00000001
+#define CMU_REG1_REFCLK_CMOS_SEL_SET(dst, src) \
+ (((dst) & ~0x00000001) | (((u32) (src) << 0) & 0x00000001))
+#define CMU_REG2 0x00004
+#define CMU_REG2_PLL_REFDIV_SET(dst, src) \
+ (((dst) & ~0x0000c000) | (((u32) (src) << 14) & 0x0000c000))
+#define CMU_REG2_PLL_LFRES_SET(dst, src) \
+ (((dst) & ~0x0000001e) | (((u32) (src) << 1) & 0x0000001e))
+#define CMU_REG2_PLL_FBDIV_SET(dst, src) \
+ (((dst) & ~0x00003fe0) | (((u32) (src) << 5) & 0x00003fe0))
+#define CMU_REG3 0x00006
+#define CMU_REG3_VCOVARSEL_SET(dst, src) \
+ (((dst) & ~0x0000000f) | (((u32) (src) << 0) & 0x0000000f))
+#define CMU_REG3_VCO_MOMSEL_INIT_SET(dst, src) \
+ (((dst) & ~0x000003f0) | (((u32) (src) << 4) & 0x000003f0))
+#define CMU_REG3_VCO_MANMOMSEL_SET(dst, src) \
+ (((dst) & ~0x0000fc00) | (((u32) (src) << 10) & 0x0000fc00))
+#define CMU_REG4 0x00008
+#define CMU_REG5 0x0000a
+#define CMU_REG5_PLL_LFSMCAP_SET(dst, src) \
+ (((dst) & ~0x0000c000) | (((u32) (src) << 14) & 0x0000c000))
+#define CMU_REG5_PLL_LOCK_RESOLUTION_SET(dst, src) \
+ (((dst) & ~0x0000000e) | (((u32) (src) << 1) & 0x0000000e))
+#define CMU_REG5_PLL_LFCAP_SET(dst, src) \
+ (((dst) & ~0x00003000) | (((u32) (src) << 12) & 0x00003000))
+#define CMU_REG5_PLL_RESETB_MASK 0x00000001
+#define CMU_REG6 0x0000c
+#define CMU_REG6_PLL_VREGTRIM_SET(dst, src) \
+ (((dst) & ~0x00000600) | (((u32) (src) << 9) & 0x00000600))
+#define CMU_REG6_MAN_PVT_CAL_SET(dst, src) \
+ (((dst) & ~0x00000004) | (((u32) (src) << 2) & 0x00000004))
+#define CMU_REG7 0x0000e
+#define CMU_REG7_PLL_CALIB_DONE_RD(src) ((0x00004000 & (u32) (src)) >> 14)
+#define CMU_REG7_VCO_CAL_FAIL_RD(src) ((0x00000c00 & (u32) (src)) >> 10)
+#define CMU_REG8 0x00010
+#define CMU_REG9 0x00012
+#define CMU_REG9_WORD_LEN_8BIT 0x000
+#define CMU_REG9_WORD_LEN_10BIT 0x001
+#define CMU_REG9_WORD_LEN_16BIT 0x002
+#define CMU_REG9_WORD_LEN_20BIT 0x003
+#define CMU_REG9_WORD_LEN_32BIT 0x004
+#define CMU_REG9_WORD_LEN_40BIT 0x005
+#define CMU_REG9_WORD_LEN_64BIT 0x006
+#define CMU_REG9_WORD_LEN_66BIT 0x007
+#define CMU_REG9_TX_WORD_MODE_CH1_SET(dst, src) \
+ (((dst) & ~0x00000380) | (((u32) (src) << 7) & 0x00000380))
+#define CMU_REG9_TX_WORD_MODE_CH0_SET(dst, src) \
+ (((dst) & ~0x00000070) | (((u32) (src) << 4) & 0x00000070))
+#define CMU_REG9_PLL_POST_DIVBY2_SET(dst, src) \
+ (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008))
+#define CMU_REG9_VBG_BYPASSB_SET(dst, src) \
+ (((dst) & ~0x00000004) | (((u32) (src) << 2) & 0x00000004))
+#define CMU_REG9_IGEN_BYPASS_SET(dst, src) \
+ (((dst) & ~0x00000002) | (((u32) (src) << 1) & 0x00000002))
+#define CMU_REG10 0x00014
+#define CMU_REG10_VREG_REFSEL_SET(dst, src) \
+ (((dst) & ~0x00000001) | (((u32) (src) << 0) & 0x00000001))
+#define CMU_REG11 0x00016
+#define CMU_REG12 0x00018
+#define CMU_REG12_STATE_DELAY9_SET(dst, src) \
+ (((dst) & ~0x000000f0) | (((u32) (src) << 4) & 0x000000f0))
+#define CMU_REG13 0x0001a
+#define CMU_REG14 0x0001c
+#define CMU_REG15 0x0001e
+#define CMU_REG16 0x00020
+#define CMU_REG16_PVT_DN_MAN_ENA_MASK 0x00000001
+#define CMU_REG16_PVT_UP_MAN_ENA_MASK 0x00000002
+#define CMU_REG16_VCOCAL_WAIT_BTW_CODE_SET(dst, src) \
+ (((dst) & ~0x0000001c) | (((u32) (src) << 2) & 0x0000001c))
+#define CMU_REG16_CALIBRATION_DONE_OVERRIDE_SET(dst, src) \
+ (((dst) & ~0x00000040) | (((u32) (src) << 6) & 0x00000040))
+#define CMU_REG16_BYPASS_PLL_LOCK_SET(dst, src) \
+ (((dst) & ~0x00000020) | (((u32) (src) << 5) & 0x00000020))
+#define CMU_REG17 0x00022
+#define CMU_REG17_PVT_CODE_R2A_SET(dst, src) \
+ (((dst) & ~0x00007f00) | (((u32) (src) << 8) & 0x00007f00))
+#define CMU_REG17_RESERVED_7_SET(dst, src) \
+ (((dst) & ~0x000000e0) | (((u32) (src) << 5) & 0x000000e0))
+#define CMU_REG17_PVT_TERM_MAN_ENA_MASK 0x00008000
+#define CMU_REG18 0x00024
+#define CMU_REG19 0x00026
+#define CMU_REG20 0x00028
+#define CMU_REG21 0x0002a
+#define CMU_REG22 0x0002c
+#define CMU_REG23 0x0002e
+#define CMU_REG24 0x00030
+#define CMU_REG25 0x00032
+#define CMU_REG26 0x00034
+#define CMU_REG26_FORCE_PLL_LOCK_SET(dst, src) \
+ (((dst) & ~0x00000001) | (((u32) (src) << 0) & 0x00000001))
+#define CMU_REG27 0x00036
+#define CMU_REG28 0x00038
+#define CMU_REG29 0x0003a
+#define CMU_REG30 0x0003c
+#define CMU_REG30_LOCK_COUNT_SET(dst, src) \
+ (((dst) & ~0x00000006) | (((u32) (src) << 1) & 0x00000006))
+#define CMU_REG30_PCIE_MODE_SET(dst, src) \
+ (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008))
+#define CMU_REG31 0x0003e
+#define CMU_REG32 0x00040
+#define CMU_REG32_FORCE_VCOCAL_START_MASK 0x00004000
+#define CMU_REG32_PVT_CAL_WAIT_SEL_SET(dst, src) \
+ (((dst) & ~0x00000006) | (((u32) (src) << 1) & 0x00000006))
+#define CMU_REG32_IREF_ADJ_SET(dst, src) \
+ (((dst) & ~0x00000180) | (((u32) (src) << 7) & 0x00000180))
+#define CMU_REG33 0x00042
+#define CMU_REG34 0x00044
+#define CMU_REG34_VCO_CAL_VTH_LO_MAX_SET(dst, src) \
+ (((dst) & ~0x0000000f) | (((u32) (src) << 0) & 0x0000000f))
+#define CMU_REG34_VCO_CAL_VTH_HI_MAX_SET(dst, src) \
+ (((dst) & ~0x00000f00) | (((u32) (src) << 8) & 0x00000f00))
+#define CMU_REG34_VCO_CAL_VTH_LO_MIN_SET(dst, src) \
+ (((dst) & ~0x000000f0) | (((u32) (src) << 4) & 0x000000f0))
+#define CMU_REG34_VCO_CAL_VTH_HI_MIN_SET(dst, src) \
+ (((dst) & ~0x0000f000) | (((u32) (src) << 12) & 0x0000f000))
+#define CMU_REG35 0x00046
+#define CMU_REG35_PLL_SSC_MOD_SET(dst, src) \
+ (((dst) & ~0x0000fe00) | (((u32) (src) << 9) & 0x0000fe00))
+#define CMU_REG36 0x00048
+#define CMU_REG36_PLL_SSC_EN_SET(dst, src) \
+ (((dst) & ~0x00000010) | (((u32) (src) << 4) & 0x00000010))
+#define CMU_REG36_PLL_SSC_VSTEP_SET(dst, src) \
+ (((dst) & ~0x0000ffc0) | (((u32) (src) << 6) & 0x0000ffc0))
+#define CMU_REG36_PLL_SSC_DSMSEL_SET(dst, src) \
+ (((dst) & ~0x00000020) | (((u32) (src) << 5) & 0x00000020))
+#define CMU_REG37 0x0004a
+#define CMU_REG38 0x0004c
+#define CMU_REG39 0x0004e
+
+/* PHY lane CSR accessing from SDS indirectly */
+#define RXTX_REG0 0x000
+#define RXTX_REG0_CTLE_EQ_HR_SET(dst, src) \
+ (((dst) & ~0x0000f800) | (((u32) (src) << 11) & 0x0000f800))
+#define RXTX_REG0_CTLE_EQ_QR_SET(dst, src) \
+ (((dst) & ~0x000007c0) | (((u32) (src) << 6) & 0x000007c0))
+#define RXTX_REG0_CTLE_EQ_FR_SET(dst, src) \
+ (((dst) & ~0x0000003e) | (((u32) (src) << 1) & 0x0000003e))
+#define RXTX_REG1 0x002
+#define RXTX_REG1_RXACVCM_SET(dst, src) \
+ (((dst) & ~0x0000f000) | (((u32) (src) << 12) & 0x0000f000))
+#define RXTX_REG1_CTLE_EQ_SET(dst, src) \
+ (((dst) & ~0x00000f80) | (((u32) (src) << 7) & 0x00000f80))
+#define RXTX_REG1_RXVREG1_SET(dst, src) \
+ (((dst) & ~0x00000060) | (((u32) (src) << 5) & 0x00000060))
+#define RXTX_REG1_RXIREF_ADJ_SET(dst, src) \
+ (((dst) & ~0x00000006) | (((u32) (src) << 1) & 0x00000006))
+#define RXTX_REG2 0x004
+#define RXTX_REG2_VTT_ENA_SET(dst, src) \
+ (((dst) & ~0x00000100) | (((u32) (src) << 8) & 0x00000100))
+#define RXTX_REG2_TX_FIFO_ENA_SET(dst, src) \
+ (((dst) & ~0x00000020) | (((u32) (src) << 5) & 0x00000020))
+#define RXTX_REG2_VTT_SEL_SET(dst, src) \
+ (((dst) & ~0x000000c0) | (((u32) (src) << 6) & 0x000000c0))
+#define RXTX_REG4 0x008
+#define RXTX_REG4_TX_LOOPBACK_BUF_EN_MASK 0x00000040
+#define RXTX_REG4_TX_DATA_RATE_SET(dst, src) \
+ (((dst) & ~0x0000c000) | (((u32) (src) << 14) & 0x0000c000))
+#define RXTX_REG4_TX_WORD_MODE_SET(dst, src) \
+ (((dst) & ~0x00003800) | (((u32) (src) << 11) & 0x00003800))
+#define RXTX_REG5 0x00a
+#define RXTX_REG5_TX_CN1_SET(dst, src) \
+ (((dst) & ~0x0000f800) | (((u32) (src) << 11) & 0x0000f800))
+#define RXTX_REG5_TX_CP1_SET(dst, src) \
+ (((dst) & ~0x000007e0) | (((u32) (src) << 5) & 0x000007e0))
+#define RXTX_REG5_TX_CN2_SET(dst, src) \
+ (((dst) & ~0x0000001f) | (((u32) (src) << 0) & 0x0000001f))
+#define RXTX_REG6 0x00c
+#define RXTX_REG6_TXAMP_CNTL_SET(dst, src) \
+ (((dst) & ~0x00000780) | (((u32) (src) << 7) & 0x00000780))
+#define RXTX_REG6_TXAMP_ENA_SET(dst, src) \
+ (((dst) & ~0x00000040) | (((u32) (src) << 6) & 0x00000040))
+#define RXTX_REG6_RX_BIST_ERRCNT_RD_SET(dst, src) \
+ (((dst) & ~0x00000001) | (((u32) (src) << 0) & 0x00000001))
+#define RXTX_REG6_TX_IDLE_SET(dst, src) \
+ (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008))
+#define RXTX_REG6_RX_BIST_RESYNC_SET(dst, src) \
+ (((dst) & ~0x00000002) | (((u32) (src) << 1) & 0x00000002))
+#define RXTX_REG7 0x00e
+#define RXTX_REG7_RESETB_RXD_MASK 0x00000100
+#define RXTX_REG7_RESETB_RXA_MASK 0x00000080
+#define RXTX_REG7_BIST_ENA_RX_SET(dst, src) \
+ (((dst) & ~0x00000040) | (((u32) (src) << 6) & 0x00000040))
+#define RXTX_REG7_RX_WORD_MODE_SET(dst, src) \
+ (((dst) & ~0x00003800) | (((u32) (src) << 11) & 0x00003800))
+#define RXTX_REG8 0x010
+#define RXTX_REG8_CDR_LOOP_ENA_SET(dst, src) \
+ (((dst) & ~0x00004000) | (((u32) (src) << 14) & 0x00004000))
+#define RXTX_REG8_CDR_BYPASS_RXLOS_SET(dst, src) \
+ (((dst) & ~0x00000800) | (((u32) (src) << 11) & 0x00000800))
+#define RXTX_REG8_SSC_ENABLE_SET(dst, src) \
+ (((dst) & ~0x00000200) | (((u32) (src) << 9) & 0x00000200))
+#define RXTX_REG8_SD_VREF_SET(dst, src) \
+ (((dst) & ~0x000000f0) | (((u32) (src) << 4) & 0x000000f0))
+#define RXTX_REG8_SD_DISABLE_SET(dst, src) \
+ (((dst) & ~0x00000100) | (((u32) (src) << 8) & 0x00000100))
+#define RXTX_REG7 0x00e
+#define RXTX_REG7_RESETB_RXD_SET(dst, src) \
+ (((dst) & ~0x00000100) | (((u32) (src) << 8) & 0x00000100))
+#define RXTX_REG7_RESETB_RXA_SET(dst, src) \
+ (((dst) & ~0x00000080) | (((u32) (src) << 7) & 0x00000080))
+#define RXTX_REG7_LOOP_BACK_ENA_CTLE_MASK 0x00004000
+#define RXTX_REG7_LOOP_BACK_ENA_CTLE_SET(dst, src) \
+ (((dst) & ~0x00004000) | (((u32) (src) << 14) & 0x00004000))
+#define RXTX_REG11 0x016
+#define RXTX_REG11_PHASE_ADJUST_LIMIT_SET(dst, src) \
+ (((dst) & ~0x0000f800) | (((u32) (src) << 11) & 0x0000f800))
+#define RXTX_REG12 0x018
+#define RXTX_REG12_LATCH_OFF_ENA_SET(dst, src) \
+ (((dst) & ~0x00002000) | (((u32) (src) << 13) & 0x00002000))
+#define RXTX_REG12_SUMOS_ENABLE_SET(dst, src) \
+ (((dst) & ~0x00000004) | (((u32) (src) << 2) & 0x00000004))
+#define RXTX_REG12_RX_DET_TERM_ENABLE_MASK 0x00000002
+#define RXTX_REG12_RX_DET_TERM_ENABLE_SET(dst, src) \
+ (((dst) & ~0x00000002) | (((u32) (src) << 1) & 0x00000002))
+#define RXTX_REG13 0x01a
+#define RXTX_REG14 0x01c
+#define RXTX_REG14_CLTE_LATCAL_MAN_PROG_SET(dst, src) \
+ (((dst) & ~0x0000003f) | (((u32) (src) << 0) & 0x0000003f))
+#define RXTX_REG14_CTLE_LATCAL_MAN_ENA_SET(dst, src) \
+ (((dst) & ~0x00000040) | (((u32) (src) << 6) & 0x00000040))
+#define RXTX_REG26 0x034
+#define RXTX_REG26_PERIOD_ERROR_LATCH_SET(dst, src) \
+ (((dst) & ~0x00003800) | (((u32) (src) << 11) & 0x00003800))
+#define RXTX_REG26_BLWC_ENA_SET(dst, src) \
+ (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008))
+#define RXTX_REG21 0x02a
+#define RXTX_REG21_DO_LATCH_CALOUT_RD(src) ((0x0000fc00 & (u32) (src)) >> 10)
+#define RXTX_REG21_XO_LATCH_CALOUT_RD(src) ((0x000003f0 & (u32) (src)) >> 4)
+#define RXTX_REG21_LATCH_CAL_FAIL_ODD_RD(src) ((0x0000000f & (u32)(src)))
+#define RXTX_REG22 0x02c
+#define RXTX_REG22_SO_LATCH_CALOUT_RD(src) ((0x000003f0 & (u32) (src)) >> 4)
+#define RXTX_REG22_EO_LATCH_CALOUT_RD(src) ((0x0000fc00 & (u32) (src)) >> 10)
+#define RXTX_REG22_LATCH_CAL_FAIL_EVEN_RD(src) ((0x0000000f & (u32)(src)))
+#define RXTX_REG23 0x02e
+#define RXTX_REG23_DE_LATCH_CALOUT_RD(src) ((0x0000fc00 & (u32) (src)) >> 10)
+#define RXTX_REG23_XE_LATCH_CALOUT_RD(src) ((0x000003f0 & (u32) (src)) >> 4)
+#define RXTX_REG24 0x030
+#define RXTX_REG24_EE_LATCH_CALOUT_RD(src) ((0x0000fc00 & (u32) (src)) >> 10)
+#define RXTX_REG24_SE_LATCH_CALOUT_RD(src) ((0x000003f0 & (u32) (src)) >> 4)
+#define RXTX_REG27 0x036
+#define RXTX_REG28 0x038
+#define RXTX_REG31 0x03e
+#define RXTX_REG38 0x04c
+#define RXTX_REG38_CUSTOMER_PINMODE_INV_SET(dst, src) \
+ (((dst) & 0x0000fffe) | (((u32) (src) << 1) & 0x0000fffe))
+#define RXTX_REG39 0x04e
+#define RXTX_REG40 0x050
+#define RXTX_REG41 0x052
+#define RXTX_REG42 0x054
+#define RXTX_REG43 0x056
+#define RXTX_REG44 0x058
+#define RXTX_REG45 0x05a
+#define RXTX_REG46 0x05c
+#define RXTX_REG47 0x05e
+#define RXTX_REG48 0x060
+#define RXTX_REG49 0x062
+#define RXTX_REG50 0x064
+#define RXTX_REG51 0x066
+#define RXTX_REG52 0x068
+#define RXTX_REG53 0x06a
+#define RXTX_REG54 0x06c
+#define RXTX_REG55 0x06e
+#define RXTX_REG61 0x07a
+#define RXTX_REG61_ISCAN_INBERT_SET(dst, src) \
+ (((dst) & ~0x00000010) | (((u32) (src) << 4) & 0x00000010))
+#define RXTX_REG61_LOADFREQ_SHIFT_SET(dst, src) \
+ (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008))
+#define RXTX_REG61_EYE_COUNT_WIDTH_SEL_SET(dst, src) \
+ (((dst) & ~0x000000c0) | (((u32) (src) << 6) & 0x000000c0))
+#define RXTX_REG61_SPD_SEL_CDR_SET(dst, src) \
+ (((dst) & ~0x00003c00) | (((u32) (src) << 10) & 0x00003c00))
+#define RXTX_REG62 0x07c
+#define RXTX_REG62_PERIOD_H1_QLATCH_SET(dst, src) \
+ (((dst) & ~0x00003800) | (((u32) (src) << 11) & 0x00003800))
+#define RXTX_REG81 0x0a2
+#define RXTX_REG89_MU_TH7_SET(dst, src) \
+ (((dst) & ~0x0000f800) | (((u32) (src) << 11) & 0x0000f800))
+#define RXTX_REG89_MU_TH8_SET(dst, src) \
+ (((dst) & ~0x000007c0) | (((u32) (src) << 6) & 0x000007c0))
+#define RXTX_REG89_MU_TH9_SET(dst, src) \
+ (((dst) & ~0x0000003e) | (((u32) (src) << 1) & 0x0000003e))
+#define RXTX_REG96 0x0c0
+#define RXTX_REG96_MU_FREQ1_SET(dst, src) \
+ (((dst) & ~0x0000f800) | (((u32) (src) << 11) & 0x0000f800))
+#define RXTX_REG96_MU_FREQ2_SET(dst, src) \
+ (((dst) & ~0x000007c0) | (((u32) (src) << 6) & 0x000007c0))
+#define RXTX_REG96_MU_FREQ3_SET(dst, src) \
+ (((dst) & ~0x0000003e) | (((u32) (src) << 1) & 0x0000003e))
+#define RXTX_REG99 0x0c6
+#define RXTX_REG99_MU_PHASE1_SET(dst, src) \
+ (((dst) & ~0x0000f800) | (((u32) (src) << 11) & 0x0000f800))
+#define RXTX_REG99_MU_PHASE2_SET(dst, src) \
+ (((dst) & ~0x000007c0) | (((u32) (src) << 6) & 0x000007c0))
+#define RXTX_REG99_MU_PHASE3_SET(dst, src) \
+ (((dst) & ~0x0000003e) | (((u32) (src) << 1) & 0x0000003e))
+#define RXTX_REG102 0x0cc
+#define RXTX_REG102_FREQLOOP_LIMIT_SET(dst, src) \
+ (((dst) & ~0x00000060) | (((u32) (src) << 5) & 0x00000060))
+#define RXTX_REG114 0x0e4
+#define RXTX_REG121 0x0f2
+#define RXTX_REG121_SUMOS_CAL_CODE_RD(src) ((0x0000003e & (u32)(src)) >> 0x1)
+#define RXTX_REG125 0x0fa
+#define RXTX_REG125_PQ_REG_SET(dst, src) \
+ (((dst) & ~0x0000fe00) | (((u32) (src) << 9) & 0x0000fe00))
+#define RXTX_REG125_SIGN_PQ_SET(dst, src) \
+ (((dst) & ~0x00000100) | (((u32) (src) << 8) & 0x00000100))
+#define RXTX_REG125_SIGN_PQ_2C_SET(dst, src) \
+ (((dst) & ~0x00000080) | (((u32) (src) << 7) & 0x00000080))
+#define RXTX_REG125_PHZ_MANUALCODE_SET(dst, src) \
+ (((dst) & ~0x0000007c) | (((u32) (src) << 2) & 0x0000007c))
+#define RXTX_REG125_PHZ_MANUAL_SET(dst, src) \
+ (((dst) & ~0x00000002) | (((u32) (src) << 1) & 0x00000002))
+#define RXTX_REG127 0x0fe
+#define RXTX_REG127_FORCE_SUM_CAL_START_MASK 0x00000002
+#define RXTX_REG127_FORCE_LAT_CAL_START_MASK 0x00000004
+#define RXTX_REG127_FORCE_SUM_CAL_START_SET(dst, src) \
+ (((dst) & ~0x00000002) | (((u32) (src) << 1) & 0x00000002))
+#define RXTX_REG127_FORCE_LAT_CAL_START_SET(dst, src) \
+ (((dst) & ~0x00000004) | (((u32) (src) << 2) & 0x00000004))
+#define RXTX_REG127_LATCH_MAN_CAL_ENA_SET(dst, src) \
+ (((dst) & ~0x00000008) | (((u32) (src) << 3) & 0x00000008))
+#define RXTX_REG127_DO_LATCH_MANCAL_SET(dst, src) \
+ (((dst) & ~0x0000fc00) | (((u32) (src) << 10) & 0x0000fc00))
+#define RXTX_REG127_XO_LATCH_MANCAL_SET(dst, src) \
+ (((dst) & ~0x000003f0) | (((u32) (src) << 4) & 0x000003f0))
+#define RXTX_REG128 0x100
+#define RXTX_REG128_LATCH_CAL_WAIT_SEL_SET(dst, src) \
+ (((dst) & ~0x0000000c) | (((u32) (src) << 2) & 0x0000000c))
+#define RXTX_REG128_EO_LATCH_MANCAL_SET(dst, src) \
+ (((dst) & ~0x0000fc00) | (((u32) (src) << 10) & 0x0000fc00))
+#define RXTX_REG128_SO_LATCH_MANCAL_SET(dst, src) \
+ (((dst) & ~0x000003f0) | (((u32) (src) << 4) & 0x000003f0))
+#define RXTX_REG129 0x102
+#define RXTX_REG129_DE_LATCH_MANCAL_SET(dst, src) \
+ (((dst) & ~0x0000fc00) | (((u32) (src) << 10) & 0x0000fc00))
+#define RXTX_REG129_XE_LATCH_MANCAL_SET(dst, src) \
+ (((dst) & ~0x000003f0) | (((u32) (src) << 4) & 0x000003f0))
+#define RXTX_REG130 0x104
+#define RXTX_REG130_EE_LATCH_MANCAL_SET(dst, src) \
+ (((dst) & ~0x0000fc00) | (((u32) (src) << 10) & 0x0000fc00))
+#define RXTX_REG130_SE_LATCH_MANCAL_SET(dst, src) \
+ (((dst) & ~0x000003f0) | (((u32) (src) << 4) & 0x000003f0))
+#define RXTX_REG145 0x122
+#define RXTX_REG145_TX_IDLE_SATA_SET(dst, src) \
+ (((dst) & ~0x00000001) | (((u32) (src) << 0) & 0x00000001))
+#define RXTX_REG145_RXES_ENA_SET(dst, src) \
+ (((dst) & ~0x00000002) | (((u32) (src) << 1) & 0x00000002))
+#define RXTX_REG145_RXDFE_CONFIG_SET(dst, src) \
+ (((dst) & ~0x0000c000) | (((u32) (src) << 14) & 0x0000c000))
+#define RXTX_REG145_RXVWES_LATENA_SET(dst, src) \
+ (((dst) & ~0x00000004) | (((u32) (src) << 2) & 0x00000004))
+#define RXTX_REG147 0x126
+#define RXTX_REG148 0x128
+
+/* Clock macro type */
+enum cmu_type_t {
+ REF_CMU = 0, /* Clock macro is the internal reference clock */
+ PHY_CMU = 1, /* Clock macro is the PLL for the Serdes */
+};
+
+enum mux_type_t {
+ MUX_SELECT_ATA = 0, /* Switch the MUX to ATA */
+ MUX_SELECT_SGMMII = 0, /* Switch the MUX to SGMII */
+};
+
+enum clk_type_t {
+ CLK_EXT_DIFF = 0, /* External differential */
+ CLK_INT_DIFF = 1, /* Internal differential */
+ CLK_INT_SING = 2, /* Internal single ended */
+};
+
+enum xgene_phy_mode {
+ MODE_SATA = 0, /* List them for simple reference */
+ MODE_SGMII = 1,
+ MODE_PCIE = 2,
+ MODE_USB = 3,
+ MODE_XFI = 4,
+ MODE_MAX
+};
+
+struct xgene_sata_override_param {
+ u32 speed[MAX_LANE]; /* Index for override parameter per lane */
+ u32 txspeed[3]; /* Tx speed */
+ u32 txboostgain[MAX_LANE*3]; /* Tx freq boost and gain control */
+ u32 txeyetuning[MAX_LANE*3]; /* Tx eye tuning */
+ u32 txeyedirection[MAX_LANE*3]; /* Tx eye tuning direction */
+ u32 txamplitude[MAX_LANE*3]; /* Tx amplitude control */
+ u32 txprecursor_cn1[MAX_LANE*3]; /* Tx emphasis taps 1st pre-cursor */
+ u32 txprecursor_cn2[MAX_LANE*3]; /* Tx emphasis taps 2nd pre-cursor */
+ u32 txpostcursor_cp1[MAX_LANE*3]; /* Tx emphasis taps post-cursor */
+};
+
+struct xgene_phy_ctx {
+ struct device *dev;
+ struct phy *phy;
+ enum xgene_phy_mode mode; /* Mode of operation */
+ enum clk_type_t clk_type; /* Input clock selection */
+ void __iomem *sds_base; /* PHY CSR base addr */
+ struct clk *clk; /* Optional clock */
+
+ /* Override Serdes parameters */
+ struct xgene_sata_override_param sata_param;
+};
+
+/*
+ * For chip earlier than A3 version, enable this flag.
+ * To enable, pass boot argument phy_xgene.preA3Chip=1
+ */
+static int preA3Chip;
+MODULE_PARM_DESC(preA3Chip, "Enable pre-A3 chip support (1=enable 0=disable)");
+module_param_named(preA3Chip, preA3Chip, int, 0444);
+
+static void sds_wr(void __iomem *csr_base, u32 indirect_cmd_reg,
+ u32 indirect_data_reg, u32 addr, u32 data)
+{
+ unsigned long deadline = jiffies + HZ;
+ u32 val;
+ u32 cmd;
+
+ cmd = CFG_IND_WR_CMD_MASK | CFG_IND_CMD_DONE_MASK;
+ cmd = CFG_IND_ADDR_SET(cmd, addr);
+ writel(data, csr_base + indirect_data_reg);
+ readl(csr_base + indirect_data_reg); /* Force a barrier */
+ writel(cmd, csr_base + indirect_cmd_reg);
+ readl(csr_base + indirect_cmd_reg); /* Force a barrier */
+ do {
+ val = readl(csr_base + indirect_cmd_reg);
+ } while (!(val & CFG_IND_CMD_DONE_MASK) &&
+ time_before(jiffies, deadline));
+ if (!(val & CFG_IND_CMD_DONE_MASK))
+ pr_err("SDS WR timeout at 0x%p offset 0x%08X value 0x%08X\n",
+ csr_base + indirect_cmd_reg, addr, data);
+}
+
+static void sds_rd(void __iomem *csr_base, u32 indirect_cmd_reg,
+ u32 indirect_data_reg, u32 addr, u32 *data)
+{
+ unsigned long deadline = jiffies + HZ;
+ u32 val;
+ u32 cmd;
+
+ cmd = CFG_IND_RD_CMD_MASK | CFG_IND_CMD_DONE_MASK;
+ cmd = CFG_IND_ADDR_SET(cmd, addr);
+ writel(cmd, csr_base + indirect_cmd_reg);
+ readl(csr_base + indirect_cmd_reg); /* Force a barrier */
+ do {
+ val = readl(csr_base + indirect_cmd_reg);
+ } while (!(val & CFG_IND_CMD_DONE_MASK) &&
+ time_before(jiffies, deadline));
+ *data = readl(csr_base + indirect_data_reg);
+ if (!(val & CFG_IND_CMD_DONE_MASK))
+ pr_err("SDS WR timeout at 0x%p offset 0x%08X value 0x%08X\n",
+ csr_base + indirect_cmd_reg, addr, *data);
+}
+
+static void cmu_wr(struct xgene_phy_ctx *ctx, enum cmu_type_t cmu_type,
+ u32 reg, u32 data)
+{
+ void __iomem *sds_base = ctx->sds_base;
+ u32 val;
+
+ if (cmu_type == REF_CMU)
+ reg += SERDES_PLL_REF_INDIRECT_OFFSET;
+ else
+ reg += SERDES_PLL_INDIRECT_OFFSET;
+ sds_wr(sds_base, SATA_ENET_SDS_IND_CMD_REG,
+ SATA_ENET_SDS_IND_WDATA_REG, reg, data);
+ sds_rd(sds_base, SATA_ENET_SDS_IND_CMD_REG,
+ SATA_ENET_SDS_IND_RDATA_REG, reg, &val);
+ pr_debug("CMU WR addr 0x%X value 0x%08X <-> 0x%08X\n", reg, data, val);
+}
+
+static void cmu_rd(struct xgene_phy_ctx *ctx, enum cmu_type_t cmu_type,
+ u32 reg, u32 *data)
+{
+ void __iomem *sds_base = ctx->sds_base;
+
+ if (cmu_type == REF_CMU)
+ reg += SERDES_PLL_REF_INDIRECT_OFFSET;
+ else
+ reg += SERDES_PLL_INDIRECT_OFFSET;
+ sds_rd(sds_base, SATA_ENET_SDS_IND_CMD_REG,
+ SATA_ENET_SDS_IND_RDATA_REG, reg, data);
+ pr_debug("CMU RD addr 0x%X value 0x%08X\n", reg, *data);
+}
+
+static void cmu_toggle1to0(struct xgene_phy_ctx *ctx, enum cmu_type_t cmu_type,
+ u32 reg, u32 bits)
+{
+ u32 val;
+
+ cmu_rd(ctx, cmu_type, reg, &val);
+ val |= bits;
+ cmu_wr(ctx, cmu_type, reg, val);
+ cmu_rd(ctx, cmu_type, reg, &val);
+ val &= ~bits;
+ cmu_wr(ctx, cmu_type, reg, val);
+}
+
+static void cmu_clrbits(struct xgene_phy_ctx *ctx, enum cmu_type_t cmu_type,
+ u32 reg, u32 bits)
+{
+ u32 val;
+
+ cmu_rd(ctx, cmu_type, reg, &val);
+ val &= ~bits;
+ cmu_wr(ctx, cmu_type, reg, val);
+}
+
+static void cmu_setbits(struct xgene_phy_ctx *ctx, enum cmu_type_t cmu_type,
+ u32 reg, u32 bits)
+{
+ u32 val;
+
+ cmu_rd(ctx, cmu_type, reg, &val);
+ val |= bits;
+ cmu_wr(ctx, cmu_type, reg, val);
+}
+
+static void serdes_wr(struct xgene_phy_ctx *ctx, int lane, u32 reg, u32 data)
+{
+ void __iomem *sds_base = ctx->sds_base;
+ u32 val;
+
+ reg += SERDES_INDIRECT_OFFSET;
+ reg += lane * SERDES_LANE_STRIDE;
+ sds_wr(sds_base, SATA_ENET_SDS_IND_CMD_REG,
+ SATA_ENET_SDS_IND_WDATA_REG, reg, data);
+ sds_rd(sds_base, SATA_ENET_SDS_IND_CMD_REG,
+ SATA_ENET_SDS_IND_RDATA_REG, reg, &val);
+ pr_debug("SERDES WR addr 0x%X value 0x%08X <-> 0x%08X\n", reg, data,
+ val);
+}
+
+static void serdes_rd(struct xgene_phy_ctx *ctx, int lane, u32 reg, u32 *data)
+{
+ void __iomem *sds_base = ctx->sds_base;
+
+ reg += SERDES_INDIRECT_OFFSET;
+ reg += lane * SERDES_LANE_STRIDE;
+ sds_rd(sds_base, SATA_ENET_SDS_IND_CMD_REG,
+ SATA_ENET_SDS_IND_RDATA_REG, reg, data);
+ pr_debug("SERDES RD addr 0x%X value 0x%08X\n", reg, *data);
+}
+
+static void serdes_clrbits(struct xgene_phy_ctx *ctx, int lane, u32 reg,
+ u32 bits)
+{
+ u32 val;
+
+ serdes_rd(ctx, lane, reg, &val);
+ val &= ~bits;
+ serdes_wr(ctx, lane, reg, val);
+}
+
+static void serdes_setbits(struct xgene_phy_ctx *ctx, int lane, u32 reg,
+ u32 bits)
+{
+ u32 val;
+
+ serdes_rd(ctx, lane, reg, &val);
+ val |= bits;
+ serdes_wr(ctx, lane, reg, val);
+}
+
+static void xgene_phy_cfg_cmu_clk_type(struct xgene_phy_ctx *ctx,
+ enum cmu_type_t cmu_type,
+ enum clk_type_t clk_type)
+{
+ u32 val;
+
+ /* Set the reset sequence delay for TX ready assertion */
+ cmu_rd(ctx, cmu_type, CMU_REG12, &val);
+ val = CMU_REG12_STATE_DELAY9_SET(val, 0x1);
+ cmu_wr(ctx, cmu_type, CMU_REG12, val);
+ /* Set the programmable stage delays between various enable stages */
+ cmu_wr(ctx, cmu_type, CMU_REG13, 0x0222);
+ cmu_wr(ctx, cmu_type, CMU_REG14, 0x2225);
+
+ /* Configure clock type */
+ if (clk_type == CLK_EXT_DIFF) {
+ /* Select external clock mux */
+ cmu_rd(ctx, cmu_type, CMU_REG0, &val);
+ val = CMU_REG0_PLL_REF_SEL_SET(val, 0x0);
+ cmu_wr(ctx, cmu_type, CMU_REG0, val);
+ /* Select CMOS as reference clock */
+ cmu_rd(ctx, cmu_type, CMU_REG1, &val);
+ val = CMU_REG1_REFCLK_CMOS_SEL_SET(val, 0x0);
+ cmu_wr(ctx, cmu_type, CMU_REG1, val);
+ dev_dbg(ctx->dev, "Set external reference clock\n");
+ } else if (clk_type == CLK_INT_DIFF) {
+ /* Select internal clock mux */
+ cmu_rd(ctx, cmu_type, CMU_REG0, &val);
+ val = CMU_REG0_PLL_REF_SEL_SET(val, 0x1);
+ cmu_wr(ctx, cmu_type, CMU_REG0, val);
+ /* Select CMOS as reference clock */
+ cmu_rd(ctx, cmu_type, CMU_REG1, &val);
+ val = CMU_REG1_REFCLK_CMOS_SEL_SET(val, 0x1);
+ cmu_wr(ctx, cmu_type, CMU_REG1, val);
+ dev_dbg(ctx->dev, "Set internal reference clock\n");
+ } else if (clk_type == CLK_INT_SING) {
+ /*
+ * NOTE: This clock type is NOT support for controller
+ * whose internal clock shared in the PCIe controller
+ *
+ * Select internal clock mux
+ */
+ cmu_rd(ctx, cmu_type, CMU_REG1, &val);
+ val = CMU_REG1_REFCLK_CMOS_SEL_SET(val, 0x1);
+ cmu_wr(ctx, cmu_type, CMU_REG1, val);
+ /* Select CML as reference clock */
+ cmu_rd(ctx, cmu_type, CMU_REG1, &val);
+ val = CMU_REG1_REFCLK_CMOS_SEL_SET(val, 0x0);
+ cmu_wr(ctx, cmu_type, CMU_REG1, val);
+ dev_dbg(ctx->dev,
+ "Set internal single ended reference clock\n");
+ }
+}
+
+static void xgene_phy_sata_cfg_cmu_core(struct xgene_phy_ctx *ctx,
+ enum cmu_type_t cmu_type,
+ enum clk_type_t clk_type)
+{
+ u32 val;
+ int ref_100MHz;
+
+ if (cmu_type == REF_CMU) {
+ /* Set VCO calibration voltage threshold */
+ cmu_rd(ctx, cmu_type, CMU_REG34, &val);
+ val = CMU_REG34_VCO_CAL_VTH_LO_MAX_SET(val, 0x7);
+ val = CMU_REG34_VCO_CAL_VTH_HI_MAX_SET(val, 0xc);
+ val = CMU_REG34_VCO_CAL_VTH_LO_MIN_SET(val, 0x3);
+ val = CMU_REG34_VCO_CAL_VTH_HI_MIN_SET(val, 0x8);
+ cmu_wr(ctx, cmu_type, CMU_REG34, val);
+ }
+
+ /* Set the VCO calibration counter */
+ cmu_rd(ctx, cmu_type, CMU_REG0, &val);
+ if (cmu_type == REF_CMU || preA3Chip)
+ val = CMU_REG0_CAL_COUNT_RESOL_SET(val, 0x4);
+ else
+ val = CMU_REG0_CAL_COUNT_RESOL_SET(val, 0x7);
+ cmu_wr(ctx, cmu_type, CMU_REG0, val);
+
+ /* Configure PLL for calibration */
+ cmu_rd(ctx, cmu_type, CMU_REG1, &val);
+ val = CMU_REG1_PLL_CP_SET(val, 0x1);
+ if (cmu_type == REF_CMU || preA3Chip)
+ val = CMU_REG1_PLL_CP_SEL_SET(val, 0x5);
+ else
+ val = CMU_REG1_PLL_CP_SEL_SET(val, 0x3);
+ if (cmu_type == REF_CMU)
+ val = CMU_REG1_PLL_MANUALCAL_SET(val, 0x0);
+ else
+ val = CMU_REG1_PLL_MANUALCAL_SET(val, 0x1);
+ cmu_wr(ctx, cmu_type, CMU_REG1, val);
+
+ if (cmu_type != REF_CMU)
+ cmu_clrbits(ctx, cmu_type, CMU_REG5, CMU_REG5_PLL_RESETB_MASK);
+
+ /* Configure the PLL for either 100MHz or 50MHz */
+ cmu_rd(ctx, cmu_type, CMU_REG2, &val);
+ if (cmu_type == REF_CMU) {
+ val = CMU_REG2_PLL_LFRES_SET(val, 0xa);
+ ref_100MHz = 1;
+ } else {
+ val = CMU_REG2_PLL_LFRES_SET(val, 0x3);
+ if (clk_type == CLK_EXT_DIFF)
+ ref_100MHz = 0;
+ else
+ ref_100MHz = 1;
+ }
+ if (ref_100MHz) {
+ val = CMU_REG2_PLL_FBDIV_SET(val, FBDIV_VAL_100M);
+ val = CMU_REG2_PLL_REFDIV_SET(val, REFDIV_VAL_100M);
+ } else {
+ val = CMU_REG2_PLL_FBDIV_SET(val, FBDIV_VAL_50M);
+ val = CMU_REG2_PLL_REFDIV_SET(val, REFDIV_VAL_50M);
+ }
+ cmu_wr(ctx, cmu_type, CMU_REG2, val);
+
+ /* Configure the VCO */
+ cmu_rd(ctx, cmu_type, CMU_REG3, &val);
+ if (cmu_type == REF_CMU) {
+ val = CMU_REG3_VCOVARSEL_SET(val, 0x3);
+ val = CMU_REG3_VCO_MOMSEL_INIT_SET(val, 0x10);
+ } else {
+ val = CMU_REG3_VCOVARSEL_SET(val, 0xF);
+ if (preA3Chip)
+ val = CMU_REG3_VCO_MOMSEL_INIT_SET(val, 0x15);
+ else
+ val = CMU_REG3_VCO_MOMSEL_INIT_SET(val, 0x1a);
+ val = CMU_REG3_VCO_MANMOMSEL_SET(val, 0x15);
+ }
+ cmu_wr(ctx, cmu_type, CMU_REG3, val);
+
+ /* Disable force PLL lock */
+ cmu_rd(ctx, cmu_type, CMU_REG26, &val);
+ val = CMU_REG26_FORCE_PLL_LOCK_SET(val, 0x0);
+ cmu_wr(ctx, cmu_type, CMU_REG26, val);
+
+ /* Setup PLL loop filter */
+ cmu_rd(ctx, cmu_type, CMU_REG5, &val);
+ val = CMU_REG5_PLL_LFSMCAP_SET(val, 0x3);
+ val = CMU_REG5_PLL_LFCAP_SET(val, 0x3);
+ if (cmu_type == REF_CMU || !preA3Chip)
+ val = CMU_REG5_PLL_LOCK_RESOLUTION_SET(val, 0x7);
+ else
+ val = CMU_REG5_PLL_LOCK_RESOLUTION_SET(val, 0x4);
+ cmu_wr(ctx, cmu_type, CMU_REG5, val);
+
+ /* Enable or disable manual calibration */
+ cmu_rd(ctx, cmu_type, CMU_REG6, &val);
+ val = CMU_REG6_PLL_VREGTRIM_SET(val, preA3Chip ? 0x0 : 0x2);
+ val = CMU_REG6_MAN_PVT_CAL_SET(val, preA3Chip ? 0x1 : 0x0);
+ cmu_wr(ctx, cmu_type, CMU_REG6, val);
+
+ /* Configure lane for 20-bits */
+ if (cmu_type == PHY_CMU) {
+ cmu_rd(ctx, cmu_type, CMU_REG9, &val);
+ val = CMU_REG9_TX_WORD_MODE_CH1_SET(val,
+ CMU_REG9_WORD_LEN_20BIT);
+ val = CMU_REG9_TX_WORD_MODE_CH0_SET(val,
+ CMU_REG9_WORD_LEN_20BIT);
+ val = CMU_REG9_PLL_POST_DIVBY2_SET(val, 0x1);
+ if (!preA3Chip) {
+ val = CMU_REG9_VBG_BYPASSB_SET(val, 0x0);
+ val = CMU_REG9_IGEN_BYPASS_SET(val , 0x0);
+ }
+ cmu_wr(ctx, cmu_type, CMU_REG9, val);
+
+ if (!preA3Chip) {
+ cmu_rd(ctx, cmu_type, CMU_REG10, &val);
+ val = CMU_REG10_VREG_REFSEL_SET(val, 0x1);
+ cmu_wr(ctx, cmu_type, CMU_REG10, val);
+ }
+ }
+
+ cmu_rd(ctx, cmu_type, CMU_REG16, &val);
+ val = CMU_REG16_CALIBRATION_DONE_OVERRIDE_SET(val, 0x1);
+ val = CMU_REG16_BYPASS_PLL_LOCK_SET(val, 0x1);
+ if (cmu_type == REF_CMU || preA3Chip)
+ val = CMU_REG16_VCOCAL_WAIT_BTW_CODE_SET(val, 0x4);
+ else
+ val = CMU_REG16_VCOCAL_WAIT_BTW_CODE_SET(val, 0x7);
+ cmu_wr(ctx, cmu_type, CMU_REG16, val);
+
+ /* Configure for SATA */
+ cmu_rd(ctx, cmu_type, CMU_REG30, &val);
+ val = CMU_REG30_PCIE_MODE_SET(val, 0x0);
+ val = CMU_REG30_LOCK_COUNT_SET(val, 0x3);
+ cmu_wr(ctx, cmu_type, CMU_REG30, val);
+
+ /* Disable state machine bypass */
+ cmu_wr(ctx, cmu_type, CMU_REG31, 0xF);
+
+ cmu_rd(ctx, cmu_type, CMU_REG32, &val);
+ val = CMU_REG32_PVT_CAL_WAIT_SEL_SET(val, 0x3);
+ if (cmu_type == REF_CMU || preA3Chip)
+ val = CMU_REG32_IREF_ADJ_SET(val, 0x3);
+ else
+ val = CMU_REG32_IREF_ADJ_SET(val, 0x1);
+ cmu_wr(ctx, cmu_type, CMU_REG32, val);
+
+ /* Set VCO calibration threshold */
+ if (cmu_type != REF_CMU && preA3Chip)
+ cmu_wr(ctx, cmu_type, CMU_REG34, 0x8d27);
+ else
+ cmu_wr(ctx, cmu_type, CMU_REG34, 0x873c);
+
+ /* Set CTLE Override and override waiting from state machine */
+ cmu_wr(ctx, cmu_type, CMU_REG37, 0xF00F);
+}
+
+static void xgene_phy_ssc_enable(struct xgene_phy_ctx *ctx,
+ enum cmu_type_t cmu_type)
+{
+ u32 val;
+
+ /* Set SSC modulation value */
+ cmu_rd(ctx, cmu_type, CMU_REG35, &val);
+ val = CMU_REG35_PLL_SSC_MOD_SET(val, 98);
+ cmu_wr(ctx, cmu_type, CMU_REG35, val);
+
+ /* Enable SSC, set vertical step and DSM value */
+ cmu_rd(ctx, cmu_type, CMU_REG36, &val);
+ val = CMU_REG36_PLL_SSC_VSTEP_SET(val, 30);
+ val = CMU_REG36_PLL_SSC_EN_SET(val, 1);
+ val = CMU_REG36_PLL_SSC_DSMSEL_SET(val, 1);
+ cmu_wr(ctx, cmu_type, CMU_REG36, val);
+
+ /* Reset the PLL */
+ cmu_clrbits(ctx, cmu_type, CMU_REG5, CMU_REG5_PLL_RESETB_MASK);
+ cmu_setbits(ctx, cmu_type, CMU_REG5, CMU_REG5_PLL_RESETB_MASK);
+
+ /* Force VCO calibration to restart */
+ cmu_toggle1to0(ctx, cmu_type, CMU_REG32,
+ CMU_REG32_FORCE_VCOCAL_START_MASK);
+}
+
+static void xgene_phy_sata_cfg_lanes(struct xgene_phy_ctx *ctx)
+{
+ u32 val;
+ u32 reg;
+ int i;
+ int lane;
+
+ for (lane = 0; lane < MAX_LANE; lane++) {
+ serdes_wr(ctx, lane, RXTX_REG147, 0x6);
+
+ /* Set boost control for quarter, half, and full rate */
+ serdes_rd(ctx, lane, RXTX_REG0, &val);
+ val = RXTX_REG0_CTLE_EQ_HR_SET(val, 0x10);
+ val = RXTX_REG0_CTLE_EQ_QR_SET(val, 0x10);
+ val = RXTX_REG0_CTLE_EQ_FR_SET(val, 0x10);
+ serdes_wr(ctx, lane, RXTX_REG0, val);
+
+ /* Set boost control value */
+ serdes_rd(ctx, lane, RXTX_REG1, &val);
+ val = RXTX_REG1_RXACVCM_SET(val, 0x7);
+ val = RXTX_REG1_CTLE_EQ_SET(val,
+ ctx->sata_param.txboostgain[lane * 3 +
+ ctx->sata_param.speed[lane]]);
+ serdes_wr(ctx, lane, RXTX_REG1, val);
+
+ /* Latch VTT value based on the termination to ground and
+ enable TX FIFO */
+ serdes_rd(ctx, lane, RXTX_REG2, &val);
+ val = RXTX_REG2_VTT_ENA_SET(val, 0x1);
+ val = RXTX_REG2_VTT_SEL_SET(val, 0x1);
+ val = RXTX_REG2_TX_FIFO_ENA_SET(val, 0x1);
+ serdes_wr(ctx, lane, RXTX_REG2, val);
+
+ /* Configure Tx for 20-bits */
+ serdes_rd(ctx, lane, RXTX_REG4, &val);
+ val = RXTX_REG4_TX_WORD_MODE_SET(val, CMU_REG9_WORD_LEN_20BIT);
+ serdes_wr(ctx, lane, RXTX_REG4, val);
+
+ if (!preA3Chip) {
+ serdes_rd(ctx, lane, RXTX_REG1, &val);
+ val = RXTX_REG1_RXVREG1_SET(val, 0x2);
+ val = RXTX_REG1_RXIREF_ADJ_SET(val, 0x2);
+ serdes_wr(ctx, lane, RXTX_REG1, val);
+ }
+
+ /* Set pre-emphasis first 1 and 2, and post-emphasis values */
+ serdes_rd(ctx, lane, RXTX_REG5, &val);
+ val = RXTX_REG5_TX_CN1_SET(val,
+ ctx->sata_param.txprecursor_cn1[lane * 3 +
+ ctx->sata_param.speed[lane]]);
+ val = RXTX_REG5_TX_CP1_SET(val,
+ ctx->sata_param.txpostcursor_cp1[lane * 3 +
+ ctx->sata_param.speed[lane]]);
+ val = RXTX_REG5_TX_CN2_SET(val,
+ ctx->sata_param.txprecursor_cn2[lane * 3 +
+ ctx->sata_param.speed[lane]]);
+ serdes_wr(ctx, lane, RXTX_REG5, val);
+
+ /* Set TX amplitude value */
+ serdes_rd(ctx, lane, RXTX_REG6, &val);
+ val = RXTX_REG6_TXAMP_CNTL_SET(val,
+ ctx->sata_param.txamplitude[lane * 3 +
+ ctx->sata_param.speed[lane]]);
+ val = RXTX_REG6_TXAMP_ENA_SET(val, 0x1);
+ val = RXTX_REG6_TX_IDLE_SET(val, 0x0);
+ val = RXTX_REG6_RX_BIST_RESYNC_SET(val, 0x0);
+ val = RXTX_REG6_RX_BIST_ERRCNT_RD_SET(val, 0x0);
+ serdes_wr(ctx, lane, RXTX_REG6, val);
+
+ /* Configure Rx for 20-bits */
+ serdes_rd(ctx, lane, RXTX_REG7, &val);
+ val = RXTX_REG7_BIST_ENA_RX_SET(val, 0x0);
+ val = RXTX_REG7_RX_WORD_MODE_SET(val, CMU_REG9_WORD_LEN_20BIT);
+ serdes_wr(ctx, lane, RXTX_REG7, val);
+
+ /* Set CDR and LOS values and enable Rx SSC */
+ serdes_rd(ctx, lane, RXTX_REG8, &val);
+ val = RXTX_REG8_CDR_LOOP_ENA_SET(val, 0x1);
+ val = RXTX_REG8_CDR_BYPASS_RXLOS_SET(val, 0x0);
+ val = RXTX_REG8_SSC_ENABLE_SET(val, 0x1);
+ val = RXTX_REG8_SD_DISABLE_SET(val, 0x0);
+ val = RXTX_REG8_SD_VREF_SET(val, 0x4);
+ serdes_wr(ctx, lane, RXTX_REG8, val);
+
+ /* Set phase adjust upper/lower limits */
+ serdes_rd(ctx, lane, RXTX_REG11, &val);
+ val = RXTX_REG11_PHASE_ADJUST_LIMIT_SET(val, 0x0);
+ serdes_wr(ctx, lane, RXTX_REG11, val);
+
+ /* Enable Latch Off; disable SUMOS and Tx termination */
+ serdes_rd(ctx, lane, RXTX_REG12, &val);
+ val = RXTX_REG12_LATCH_OFF_ENA_SET(val, 0x1);
+ val = RXTX_REG12_SUMOS_ENABLE_SET(val, 0x0);
+ val = RXTX_REG12_RX_DET_TERM_ENABLE_SET(val, 0x0);
+ serdes_wr(ctx, lane, RXTX_REG12, val);
+
+ /* Set period error latch to 512T and enable BWL */
+ serdes_rd(ctx, lane, RXTX_REG26, &val);
+ val = RXTX_REG26_PERIOD_ERROR_LATCH_SET(val, 0x0);
+ val = RXTX_REG26_BLWC_ENA_SET(val, 0x1);
+ serdes_wr(ctx, lane, RXTX_REG26, val);
+
+ serdes_wr(ctx, lane, RXTX_REG28, 0x0);
+
+ /* Set DFE loop preset value */
+ serdes_wr(ctx, lane, RXTX_REG31, 0x0);
+
+ /* Set Eye Monitor counter width to 12-bit */
+ serdes_rd(ctx, lane, RXTX_REG61, &val);
+ val = RXTX_REG61_ISCAN_INBERT_SET(val, 0x1);
+ val = RXTX_REG61_LOADFREQ_SHIFT_SET(val, 0x0);
+ val = RXTX_REG61_EYE_COUNT_WIDTH_SEL_SET(val, 0x0);
+ serdes_wr(ctx, lane, RXTX_REG61, val);
+
+ serdes_rd(ctx, lane, RXTX_REG62, &val);
+ val = RXTX_REG62_PERIOD_H1_QLATCH_SET(val, 0x0);
+ serdes_wr(ctx, lane, RXTX_REG62, val);
+
+ /* Set BW select tap X for DFE loop */
+ for (i = 0; i < 9; i++) {
+ reg = RXTX_REG81 + i * 2;
+ serdes_rd(ctx, lane, reg, &val);
+ val = RXTX_REG89_MU_TH7_SET(val, 0xe);
+ val = RXTX_REG89_MU_TH8_SET(val, 0xe);
+ val = RXTX_REG89_MU_TH9_SET(val, 0xe);
+ serdes_wr(ctx, lane, reg, val);
+ }
+
+ /* Set BW select tap X for frequency adjust loop */
+ for (i = 0; i < 3; i++) {
+ reg = RXTX_REG96 + i * 2;
+ serdes_rd(ctx, lane, reg, &val);
+ val = RXTX_REG96_MU_FREQ1_SET(val, 0x10);
+ val = RXTX_REG96_MU_FREQ2_SET(val, 0x10);
+ val = RXTX_REG96_MU_FREQ3_SET(val, 0x10);
+ serdes_wr(ctx, lane, reg, val);
+ }
+
+ /* Set BW select tap X for phase adjust loop */
+ for (i = 0; i < 3; i++) {
+ reg = RXTX_REG99 + i * 2;
+ serdes_rd(ctx, lane, reg, &val);
+ val = RXTX_REG99_MU_PHASE1_SET(val, 0x7);
+ val = RXTX_REG99_MU_PHASE2_SET(val, 0x7);
+ val = RXTX_REG99_MU_PHASE3_SET(val, 0x7);
+ serdes_wr(ctx, lane, reg, val);
+ }
+
+ serdes_rd(ctx, lane, RXTX_REG102, &val);
+ val = RXTX_REG102_FREQLOOP_LIMIT_SET(val, 0x0);
+ serdes_wr(ctx, lane, RXTX_REG102, val);
+
+ serdes_wr(ctx, lane, RXTX_REG114, 0xffe0);
+
+ serdes_rd(ctx, lane, RXTX_REG125, &val);
+ val = RXTX_REG125_SIGN_PQ_SET(val,
+ ctx->sata_param.txeyedirection[lane * 3 +
+ ctx->sata_param.speed[lane]]);
+ val = RXTX_REG125_PQ_REG_SET(val,
+ ctx->sata_param.txeyetuning[lane * 3 +
+ ctx->sata_param.speed[lane]]);
+ val = RXTX_REG125_PHZ_MANUAL_SET(val, 0x1);
+ serdes_wr(ctx, lane, RXTX_REG125, val);
+
+ serdes_rd(ctx, lane, RXTX_REG127, &val);
+ val = RXTX_REG127_LATCH_MAN_CAL_ENA_SET(val, 0x0);
+ serdes_wr(ctx, lane, RXTX_REG127, val);
+
+ serdes_rd(ctx, lane, RXTX_REG128, &val);
+ val = RXTX_REG128_LATCH_CAL_WAIT_SEL_SET(val, 0x3);
+ serdes_wr(ctx, lane, RXTX_REG128, val);
+
+ serdes_rd(ctx, lane, RXTX_REG145, &val);
+ val = RXTX_REG145_RXDFE_CONFIG_SET(val, 0x3);
+ val = RXTX_REG145_TX_IDLE_SATA_SET(val, 0x0);
+ if (preA3Chip) {
+ val = RXTX_REG145_RXES_ENA_SET(val, 0x1);
+ val = RXTX_REG145_RXVWES_LATENA_SET(val, 0x1);
+ } else {
+ val = RXTX_REG145_RXES_ENA_SET(val, 0x0);
+ val = RXTX_REG145_RXVWES_LATENA_SET(val, 0x0);
+ }
+ serdes_wr(ctx, lane, RXTX_REG145, val);
+
+ /*
+ * Set Rx LOS filter clock rate, sample rate, and threshold
+ * windows
+ */
+ for (i = 0; i < 4; i++) {
+ reg = RXTX_REG148 + i * 2;
+ serdes_wr(ctx, lane, reg, 0xFFFF);
+ }
+ }
+}
+
+static int xgene_phy_cal_rdy_chk(struct xgene_phy_ctx *ctx,
+ enum cmu_type_t cmu_type,
+ enum clk_type_t clk_type)
+{
+ void __iomem *csr_serdes = ctx->sds_base;
+ int loop;
+ u32 val;
+
+ /* Release PHY main reset */
+ writel(0xdf, csr_serdes + SATA_ENET_SDS_RST_CTL);
+ readl(csr_serdes + SATA_ENET_SDS_RST_CTL); /* Force a barrier */
+
+ if (cmu_type != REF_CMU) {
+ cmu_setbits(ctx, cmu_type, CMU_REG5, CMU_REG5_PLL_RESETB_MASK);
+ /*
+ * As per PHY design spec, the PLL reset requires a minimum
+ * of 800us.
+ */
+ usleep_range(800, 1000);
+
+ cmu_rd(ctx, cmu_type, CMU_REG1, &val);
+ val = CMU_REG1_PLL_MANUALCAL_SET(val, 0x0);
+ cmu_wr(ctx, cmu_type, CMU_REG1, val);
+ /*
+ * As per PHY design spec, the PLL auto calibration requires
+ * a minimum of 800us.
+ */
+ usleep_range(800, 1000);
+
+ cmu_toggle1to0(ctx, cmu_type, CMU_REG32,
+ CMU_REG32_FORCE_VCOCAL_START_MASK);
+ /*
+ * As per PHY design spec, the PLL requires a minimum of
+ * 800us to settle.
+ */
+ usleep_range(800, 1000);
+ }
+
+ if (!preA3Chip)
+ goto skip_manual_cal;
+
+ /*
+ * Configure the termination resister calibration
+ * The serial receive pins, RXP/RXN, have TERMination resistor
+ * that is required to be calibrated.
+ */
+ cmu_rd(ctx, cmu_type, CMU_REG17, &val);
+ val = CMU_REG17_PVT_CODE_R2A_SET(val, 0x12);
+ val = CMU_REG17_RESERVED_7_SET(val, 0x0);
+ cmu_wr(ctx, cmu_type, CMU_REG17, val);
+ cmu_toggle1to0(ctx, cmu_type, CMU_REG17,
+ CMU_REG17_PVT_TERM_MAN_ENA_MASK);
+ /*
+ * The serial transmit pins, TXP/TXN, have Pull-UP and Pull-DOWN
+ * resistors that are required to the calibrated.
+ * Configure the pull DOWN calibration
+ */
+ cmu_rd(ctx, cmu_type, CMU_REG17, &val);
+ val = CMU_REG17_PVT_CODE_R2A_SET(val, 0x29);
+ val = CMU_REG17_RESERVED_7_SET(val, 0x0);
+ cmu_wr(ctx, cmu_type, CMU_REG17, val);
+ cmu_toggle1to0(ctx, cmu_type, CMU_REG16,
+ CMU_REG16_PVT_DN_MAN_ENA_MASK);
+ /* Configure the pull UP calibration */
+ cmu_rd(ctx, cmu_type, CMU_REG17, &val);
+ val = CMU_REG17_PVT_CODE_R2A_SET(val, 0x28);
+ val = CMU_REG17_RESERVED_7_SET(val, 0x0);
+ cmu_wr(ctx, cmu_type, CMU_REG17, val);
+ cmu_toggle1to0(ctx, cmu_type, CMU_REG16,
+ CMU_REG16_PVT_UP_MAN_ENA_MASK);
+
+skip_manual_cal:
+ /* Poll the PLL calibration completion status for at least 1 ms */
+ loop = 100;
+ do {
+ cmu_rd(ctx, cmu_type, CMU_REG7, &val);
+ if (CMU_REG7_PLL_CALIB_DONE_RD(val))
+ break;
+ /*
+ * As per PHY design spec, PLL calibration status requires
+ * a minimum of 10us to be updated.
+ */
+ usleep_range(10, 100);
+ } while (--loop > 0);
+
+ cmu_rd(ctx, cmu_type, CMU_REG7, &val);
+ dev_dbg(ctx->dev, "PLL calibration %s\n",
+ CMU_REG7_PLL_CALIB_DONE_RD(val) ? "done" : "failed");
+ if (CMU_REG7_VCO_CAL_FAIL_RD(val)) {
+ dev_err(ctx->dev,
+ "PLL calibration failed due to VCO failure\n");
+ return -1;
+ }
+ dev_dbg(ctx->dev, "PLL calibration successful\n");
+
+ cmu_rd(ctx, cmu_type, CMU_REG15, &val);
+ dev_dbg(ctx->dev, "PHY Tx is %sready\n", val & 0x300 ? "" : "not ");
+ return 0;
+}
+
+static void xgene_phy_pdwn_force_vco(struct xgene_phy_ctx *ctx,
+ enum cmu_type_t cmu_type,
+ enum clk_type_t clk_type)
+{
+ u32 val;
+
+ dev_dbg(ctx->dev, "Reset VCO and re-start again\n");
+ if (cmu_type == PHY_CMU) {
+ cmu_rd(ctx, cmu_type, CMU_REG16, &val);
+ val = CMU_REG16_VCOCAL_WAIT_BTW_CODE_SET(val, 0x7);
+ cmu_wr(ctx, cmu_type, CMU_REG16, val);
+ }
+
+ cmu_toggle1to0(ctx, cmu_type, CMU_REG0, CMU_REG0_PDOWN_MASK);
+ cmu_toggle1to0(ctx, cmu_type, CMU_REG32,
+ CMU_REG32_FORCE_VCOCAL_START_MASK);
+}
+
+static int xgene_phy_hw_init_sata(struct xgene_phy_ctx *ctx,
+ enum clk_type_t clk_type, int ssc_enable)
+{
+ void __iomem *sds_base = ctx->sds_base;
+ u32 val;
+ int i;
+
+ /* Configure the PHY for operation */
+ dev_dbg(ctx->dev, "Reset PHY\n");
+ /* Place PHY into reset */
+ writel(0x0, sds_base + SATA_ENET_SDS_RST_CTL);
+ val = readl(sds_base + SATA_ENET_SDS_RST_CTL); /* Force a barrier */
+ /* Release PHY lane from reset (active high) */
+ writel(0x20, sds_base + SATA_ENET_SDS_RST_CTL);
+ readl(sds_base + SATA_ENET_SDS_RST_CTL); /* Force a barrier */
+ /* Release all PHY module out of reset except PHY main reset */
+ writel(0xde, sds_base + SATA_ENET_SDS_RST_CTL);
+ readl(sds_base + SATA_ENET_SDS_RST_CTL); /* Force a barrier */
+
+ /* Set the operation speed */
+ val = readl(sds_base + SATA_ENET_SDS_CTL1);
+ val = CFG_I_SPD_SEL_CDR_OVR1_SET(val,
+ ctx->sata_param.txspeed[ctx->sata_param.speed[0]]);
+ writel(val, sds_base + SATA_ENET_SDS_CTL1);
+
+ dev_dbg(ctx->dev, "Set the customer pin mode to SATA\n");
+ val = readl(sds_base + SATA_ENET_SDS_CTL0);
+ val = REGSPEC_CFG_I_CUSTOMER_PIN_MODE0_SET(val, 0x4421);
+ writel(val, sds_base + SATA_ENET_SDS_CTL0);
+
+ /* Configure the clock macro unit (CMU) clock type */
+ xgene_phy_cfg_cmu_clk_type(ctx, PHY_CMU, clk_type);
+
+ /* Configure the clock macro */
+ xgene_phy_sata_cfg_cmu_core(ctx, PHY_CMU, clk_type);
+
+ /* Enable SSC if enabled */
+ if (ssc_enable)
+ xgene_phy_ssc_enable(ctx, PHY_CMU);
+
+ /* Configure PHY lanes */
+ xgene_phy_sata_cfg_lanes(ctx);
+
+ /* Set Rx/Tx 20-bit */
+ val = readl(sds_base + SATA_ENET_SDS_PCS_CTL0);
+ val = REGSPEC_CFG_I_RX_WORDMODE0_SET(val, 0x3);
+ val = REGSPEC_CFG_I_TX_WORDMODE0_SET(val, 0x3);
+ writel(val, sds_base + SATA_ENET_SDS_PCS_CTL0);
+
+ /* Start PLL calibration and try for three times */
+ i = 10;
+ do {
+ if (!xgene_phy_cal_rdy_chk(ctx, PHY_CMU, clk_type))
+ break;
+ /* If failed, toggle the VCO power signal and start again */
+ xgene_phy_pdwn_force_vco(ctx, PHY_CMU, clk_type);
+ } while (--i > 0);
+ /* Even on failure, allow to continue any way */
+ if (i <= 0)
+ dev_err(ctx->dev, "PLL calibration failed\n");
+
+ return 0;
+}
+
+static int xgene_phy_hw_initialize(struct xgene_phy_ctx *ctx,
+ enum clk_type_t clk_type,
+ int ssc_enable)
+{
+ int rc;
+
+ dev_dbg(ctx->dev, "PHY init clk type %d\n", clk_type);
+
+ if (ctx->mode == MODE_SATA) {
+ rc = xgene_phy_hw_init_sata(ctx, clk_type, ssc_enable);
+ if (rc)
+ return rc;
+ } else {
+ dev_err(ctx->dev, "Un-supported customer pin mode %d\n",
+ ctx->mode);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+/*
+ * Receiver Offset Calibration:
+ *
+ * Calibrate the receiver signal path offset in two steps - summar and
+ * latch calibrations
+ */
+static void xgene_phy_force_lat_summer_cal(struct xgene_phy_ctx *ctx, int lane)
+{
+ int i;
+ struct {
+ u32 reg;
+ u32 val;
+ } serdes_reg[] = {
+ {RXTX_REG38, 0x0},
+ {RXTX_REG39, 0xff00},
+ {RXTX_REG40, 0xffff},
+ {RXTX_REG41, 0xffff},
+ {RXTX_REG42, 0xffff},
+ {RXTX_REG43, 0xffff},
+ {RXTX_REG44, 0xffff},
+ {RXTX_REG45, 0xffff},
+ {RXTX_REG46, 0xffff},
+ {RXTX_REG47, 0xfffc},
+ {RXTX_REG48, 0x0},
+ {RXTX_REG49, 0x0},
+ {RXTX_REG50, 0x0},
+ {RXTX_REG51, 0x0},
+ {RXTX_REG52, 0x0},
+ {RXTX_REG53, 0x0},
+ {RXTX_REG54, 0x0},
+ {RXTX_REG55, 0x0},
+ };
+
+ /* Start SUMMER calibration */
+ serdes_setbits(ctx, lane, RXTX_REG127,
+ RXTX_REG127_FORCE_SUM_CAL_START_MASK);
+ /*
+ * As per PHY design spec, the Summer calibration requires a minimum
+ * of 100us to complete.
+ */
+ usleep_range(100, 500);
+ serdes_clrbits(ctx, lane, RXTX_REG127,
+ RXTX_REG127_FORCE_SUM_CAL_START_MASK);
+ /*
+ * As per PHY design spec, the auto calibration requires a minimum
+ * of 100us to complete.
+ */
+ usleep_range(100, 500);
+
+ /* Start latch calibration */
+ serdes_setbits(ctx, lane, RXTX_REG127,
+ RXTX_REG127_FORCE_LAT_CAL_START_MASK);
+ /*
+ * As per PHY design spec, the latch calibration requires a minimum
+ * of 100us to complete.
+ */
+ usleep_range(100, 500);
+ serdes_clrbits(ctx, lane, RXTX_REG127,
+ RXTX_REG127_FORCE_LAT_CAL_START_MASK);
+
+ /* Configure the PHY lane for calibration */
+ serdes_wr(ctx, lane, RXTX_REG28, 0x7);
+ serdes_wr(ctx, lane, RXTX_REG31, 0x7e00);
+ serdes_clrbits(ctx, lane, RXTX_REG4,
+ RXTX_REG4_TX_LOOPBACK_BUF_EN_MASK);
+ serdes_clrbits(ctx, lane, RXTX_REG7,
+ RXTX_REG7_LOOP_BACK_ENA_CTLE_MASK);
+ for (i = 0; i < ARRAY_SIZE(serdes_reg); i++)
+ serdes_wr(ctx, lane, serdes_reg[i].reg,
+ serdes_reg[i].val);
+}
+
+static void xgene_phy_reset_rxd(struct xgene_phy_ctx *ctx, int lane)
+{
+ /* Reset digital Rx */
+ serdes_clrbits(ctx, lane, RXTX_REG7, RXTX_REG7_RESETB_RXD_MASK);
+ /* As per PHY design spec, the reset requires a minimum of 100us. */
+ usleep_range(100, 150);
+ serdes_setbits(ctx, lane, RXTX_REG7, RXTX_REG7_RESETB_RXD_MASK);
+}
+
+static int xgene_phy_get_avg(int accum, int samples)
+{
+ return (accum + (samples / 2)) / samples;
+}
+
+static void xgene_phy_gen_avg_val(struct xgene_phy_ctx *ctx, int lane)
+{
+ int max_loop = 10;
+ int avg_loop = 0;
+ int lat_do = 0, lat_xo = 0, lat_eo = 0, lat_so = 0;
+ int lat_de = 0, lat_xe = 0, lat_ee = 0, lat_se = 0;
+ int sum_cal = 0;
+ int lat_do_itr, lat_xo_itr, lat_eo_itr, lat_so_itr;
+ int lat_de_itr, lat_xe_itr, lat_ee_itr, lat_se_itr;
+ int sum_cal_itr;
+ int fail_even;
+ int fail_odd;
+ u32 val;
+
+ dev_dbg(ctx->dev, "Generating avg calibration value for lane %d\n",
+ lane);
+
+ /* Enable RX Hi-Z termination */
+ serdes_setbits(ctx, lane, RXTX_REG12,
+ RXTX_REG12_RX_DET_TERM_ENABLE_MASK);
+ /* Turn off DFE */
+ serdes_wr(ctx, lane, RXTX_REG28, 0x0000);
+ /* DFE Presets to zero */
+ serdes_wr(ctx, lane, RXTX_REG31, 0x0000);
+
+ /*
+ * Receiver Offset Calibration:
+ * Calibrate the receiver signal path offset in two steps - summar
+ * and latch calibration.
+ * Runs the "Receiver Offset Calibration multiple times to determine
+ * the average value to use.
+ */
+ while (avg_loop < max_loop) {
+ /* Start the calibration */
+ xgene_phy_force_lat_summer_cal(ctx, lane);
+
+ serdes_rd(ctx, lane, RXTX_REG21, &val);
+ lat_do_itr = RXTX_REG21_DO_LATCH_CALOUT_RD(val);
+ lat_xo_itr = RXTX_REG21_XO_LATCH_CALOUT_RD(val);
+ fail_odd = RXTX_REG21_LATCH_CAL_FAIL_ODD_RD(val);
+
+ serdes_rd(ctx, lane, RXTX_REG22, &val);
+ lat_eo_itr = RXTX_REG22_EO_LATCH_CALOUT_RD(val);
+ lat_so_itr = RXTX_REG22_SO_LATCH_CALOUT_RD(val);
+ fail_even = RXTX_REG22_LATCH_CAL_FAIL_EVEN_RD(val);
+
+ serdes_rd(ctx, lane, RXTX_REG23, &val);
+ lat_de_itr = RXTX_REG23_DE_LATCH_CALOUT_RD(val);
+ lat_xe_itr = RXTX_REG23_XE_LATCH_CALOUT_RD(val);
+
+ serdes_rd(ctx, lane, RXTX_REG24, &val);
+ lat_ee_itr = RXTX_REG24_EE_LATCH_CALOUT_RD(val);
+ lat_se_itr = RXTX_REG24_SE_LATCH_CALOUT_RD(val);
+
+ serdes_rd(ctx, lane, RXTX_REG121, &val);
+ sum_cal_itr = RXTX_REG121_SUMOS_CAL_CODE_RD(val);
+
+ /* Check for failure. If passed, sum them for averaging */
+ if ((fail_even == 0 || fail_even == 1) &&
+ (fail_odd == 0 || fail_odd == 1)) {
+ lat_do += lat_do_itr;
+ lat_xo += lat_xo_itr;
+ lat_eo += lat_eo_itr;
+ lat_so += lat_so_itr;
+ lat_de += lat_de_itr;
+ lat_xe += lat_xe_itr;
+ lat_ee += lat_ee_itr;
+ lat_se += lat_se_itr;
+ sum_cal += sum_cal_itr;
+
+ dev_dbg(ctx->dev, "Iteration %d:\n", avg_loop);
+ dev_dbg(ctx->dev, "DO 0x%x XO 0x%x EO 0x%x SO 0x%x\n",
+ lat_do_itr, lat_xo_itr, lat_eo_itr,
+ lat_so_itr);
+ dev_dbg(ctx->dev, "DE 0x%x XE 0x%x EE 0x%x SE 0x%x\n",
+ lat_de_itr, lat_xe_itr, lat_ee_itr,
+ lat_se_itr);
+ dev_dbg(ctx->dev, "SUM 0x%x\n", sum_cal_itr);
+ ++avg_loop;
+ } else {
+ dev_err(ctx->dev,
+ "Receiver calibration failed at %d loop\n",
+ avg_loop);
+ }
+ xgene_phy_reset_rxd(ctx, lane);
+ }
+
+ /* Update latch manual calibration with average value */
+ serdes_rd(ctx, lane, RXTX_REG127, &val);
+ val = RXTX_REG127_DO_LATCH_MANCAL_SET(val,
+ xgene_phy_get_avg(lat_do, max_loop));
+ val = RXTX_REG127_XO_LATCH_MANCAL_SET(val,
+ xgene_phy_get_avg(lat_xo, max_loop));
+ serdes_wr(ctx, lane, RXTX_REG127, val);
+
+ serdes_rd(ctx, lane, RXTX_REG128, &val);
+ val = RXTX_REG128_EO_LATCH_MANCAL_SET(val,
+ xgene_phy_get_avg(lat_eo, max_loop));
+ val = RXTX_REG128_SO_LATCH_MANCAL_SET(val,
+ xgene_phy_get_avg(lat_so, max_loop));
+ serdes_wr(ctx, lane, RXTX_REG128, val);
+
+ serdes_rd(ctx, lane, RXTX_REG129, &val);
+ val = RXTX_REG129_DE_LATCH_MANCAL_SET(val,
+ xgene_phy_get_avg(lat_de, max_loop));
+ val = RXTX_REG129_XE_LATCH_MANCAL_SET(val,
+ xgene_phy_get_avg(lat_xe, max_loop));
+ serdes_wr(ctx, lane, RXTX_REG129, val);
+
+ serdes_rd(ctx, lane, RXTX_REG130, &val);
+ val = RXTX_REG130_EE_LATCH_MANCAL_SET(val,
+ xgene_phy_get_avg(lat_ee, max_loop));
+ val = RXTX_REG130_SE_LATCH_MANCAL_SET(val,
+ xgene_phy_get_avg(lat_se, max_loop));
+ serdes_wr(ctx, lane, RXTX_REG130, val);
+
+ /* Update SUMMER calibration with average value */
+ serdes_rd(ctx, lane, RXTX_REG14, &val);
+ val = RXTX_REG14_CLTE_LATCAL_MAN_PROG_SET(val,
+ xgene_phy_get_avg(sum_cal, max_loop));
+ serdes_wr(ctx, lane, RXTX_REG14, val);
+
+ dev_dbg(ctx->dev, "Average Value:\n");
+ dev_dbg(ctx->dev, "DO 0x%x XO 0x%x EO 0x%x SO 0x%x\n",
+ xgene_phy_get_avg(lat_do, max_loop),
+ xgene_phy_get_avg(lat_xo, max_loop),
+ xgene_phy_get_avg(lat_eo, max_loop),
+ xgene_phy_get_avg(lat_so, max_loop));
+ dev_dbg(ctx->dev, "DE 0x%x XE 0x%x EE 0x%x SE 0x%x\n",
+ xgene_phy_get_avg(lat_de, max_loop),
+ xgene_phy_get_avg(lat_xe, max_loop),
+ xgene_phy_get_avg(lat_ee, max_loop),
+ xgene_phy_get_avg(lat_se, max_loop));
+ dev_dbg(ctx->dev, "SUM 0x%x\n",
+ xgene_phy_get_avg(sum_cal, max_loop));
+
+ serdes_rd(ctx, lane, RXTX_REG14, &val);
+ val = RXTX_REG14_CTLE_LATCAL_MAN_ENA_SET(val, 0x1);
+ serdes_wr(ctx, lane, RXTX_REG14, val);
+ dev_dbg(ctx->dev, "Enable Manual Summer calibration\n");
+
+ serdes_rd(ctx, lane, RXTX_REG127, &val);
+ val = RXTX_REG127_LATCH_MAN_CAL_ENA_SET(val, 0x1);
+ dev_dbg(ctx->dev, "Enable Manual Latch calibration\n");
+ serdes_wr(ctx, lane, RXTX_REG127, val);
+
+ /* Disable RX Hi-Z termination */
+ serdes_rd(ctx, lane, RXTX_REG12, &val);
+ val = RXTX_REG12_RX_DET_TERM_ENABLE_SET(val, 0);
+ serdes_wr(ctx, lane, RXTX_REG12, val);
+ /* Turn on DFE */
+ serdes_wr(ctx, lane, RXTX_REG28, 0x0007);
+ /* Set DFE preset */
+ serdes_wr(ctx, lane, RXTX_REG31, 0x7e00);
+}
+
+static int xgene_phy_hw_init(struct phy *phy)
+{
+ struct xgene_phy_ctx *ctx = phy_get_drvdata(phy);
+ int rc;
+ int i;
+
+ rc = xgene_phy_hw_initialize(ctx, CLK_EXT_DIFF, SSC_DISABLE);
+ if (rc) {
+ dev_err(ctx->dev, "PHY initialize failed %d\n", rc);
+ return rc;
+ }
+
+ /* Setup clock properly after PHY configuration */
+ if (!IS_ERR(ctx->clk)) {
+ /* HW requires an toggle of the clock */
+ clk_prepare_enable(ctx->clk);
+ clk_disable_unprepare(ctx->clk);
+ clk_prepare_enable(ctx->clk);
+ }
+
+ /* Compute average value */
+ for (i = 0; i < MAX_LANE; i++)
+ xgene_phy_gen_avg_val(ctx, i);
+
+ dev_dbg(ctx->dev, "PHY initialized\n");
+ return 0;
+}
+
+static const struct phy_ops xgene_phy_ops = {
+ .init = xgene_phy_hw_init,
+ .owner = THIS_MODULE,
+};
+
+static struct phy *xgene_phy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct xgene_phy_ctx *ctx = dev_get_drvdata(dev);
+
+ if (args->args_count <= 0)
+ return ERR_PTR(-EINVAL);
+ if (args->args[0] < MODE_SATA || args->args[0] >= MODE_MAX)
+ return ERR_PTR(-EINVAL);
+
+ ctx->mode = args->args[0];
+ return ctx->phy;
+}
+
+static void xgene_phy_get_param(struct platform_device *pdev,
+ const char *name, u32 *buffer,
+ int count, u32 *default_val,
+ u32 conv_factor)
+{
+ int i;
+
+ if (!of_property_read_u32_array(pdev->dev.of_node, name, buffer,
+ count)) {
+ for (i = 0; i < count; i++)
+ buffer[i] /= conv_factor;
+ return;
+ }
+ /* Does not exist, load default */
+ for (i = 0; i < count; i++)
+ buffer[i] = default_val[i % 3];
+}
+
+static int xgene_phy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct xgene_phy_ctx *ctx;
+ struct resource *res;
+ u32 default_spd[] = DEFAULT_SATA_SPD_SEL;
+ u32 default_txboost_gain[] = DEFAULT_SATA_TXBOOST_GAIN;
+ u32 default_txeye_direction[] = DEFAULT_SATA_TXEYEDIRECTION;
+ u32 default_txeye_tuning[] = DEFAULT_SATA_TXEYETUNING;
+ u32 default_txamp[] = DEFAULT_SATA_TXAMP;
+ u32 default_txcn1[] = DEFAULT_SATA_TXCN1;
+ u32 default_txcn2[] = DEFAULT_SATA_TXCN2;
+ u32 default_txcp1[] = DEFAULT_SATA_TXCP1;
+ int i;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->dev = &pdev->dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ ctx->sds_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(ctx->sds_base))
+ return PTR_ERR(ctx->sds_base);
+
+ /* Retrieve optional clock */
+ ctx->clk = clk_get(&pdev->dev, NULL);
+
+ /* Load override paramaters */
+ xgene_phy_get_param(pdev, "apm,tx-eye-tuning",
+ ctx->sata_param.txeyetuning, 6, default_txeye_tuning, 1);
+ xgene_phy_get_param(pdev, "apm,tx-eye-direction",
+ ctx->sata_param.txeyedirection, 6, default_txeye_direction, 1);
+ xgene_phy_get_param(pdev, "apm,tx-boost-gain",
+ ctx->sata_param.txboostgain, 6, default_txboost_gain, 1);
+ xgene_phy_get_param(pdev, "apm,tx-amplitude",
+ ctx->sata_param.txamplitude, 6, default_txamp, 13300);
+ xgene_phy_get_param(pdev, "apm,tx-pre-cursor1",
+ ctx->sata_param.txprecursor_cn1, 6, default_txcn1, 18200);
+ xgene_phy_get_param(pdev, "apm,tx-pre-cursor2",
+ ctx->sata_param.txprecursor_cn2, 6, default_txcn2, 18200);
+ xgene_phy_get_param(pdev, "apm,tx-post-cursor",
+ ctx->sata_param.txpostcursor_cp1, 6, default_txcp1, 18200);
+ xgene_phy_get_param(pdev, "apm,tx-speed",
+ ctx->sata_param.txspeed, 3, default_spd, 1);
+ for (i = 0; i < MAX_LANE; i++)
+ ctx->sata_param.speed[i] = 2; /* Default to Gen3 */
+
+ platform_set_drvdata(pdev, ctx);
+
+ ctx->phy = devm_phy_create(ctx->dev, NULL, &xgene_phy_ops);
+ if (IS_ERR(ctx->phy)) {
+ dev_dbg(&pdev->dev, "Failed to create PHY\n");
+ return PTR_ERR(ctx->phy);
+ }
+ phy_set_drvdata(ctx->phy, ctx);
+
+ phy_provider = devm_of_phy_provider_register(ctx->dev, xgene_phy_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id xgene_phy_of_match[] = {
+ {.compatible = "apm,xgene-phy",},
+ {},
+};
+MODULE_DEVICE_TABLE(of, xgene_phy_of_match);
+
+static struct platform_driver xgene_phy_driver = {
+ .probe = xgene_phy_probe,
+ .driver = {
+ .name = "xgene-phy",
+ .of_match_table = xgene_phy_of_match,
+ },
+};
+module_platform_driver(xgene_phy_driver);
+
+MODULE_DESCRIPTION("APM X-Gene Multi-Purpose PHY driver");
+MODULE_AUTHOR("Loc Ho <lho@apm.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("0.1");
diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig
new file mode 100644
index 000000000..632a0e73e
--- /dev/null
+++ b/drivers/phy/qualcomm/Kconfig
@@ -0,0 +1,67 @@
+#
+# Phy drivers for Qualcomm and Atheros platforms
+#
+config PHY_ATH79_USB
+ tristate "Atheros AR71XX/9XXX USB PHY driver"
+ depends on OF && (ATH79 || COMPILE_TEST)
+ default y if USB_EHCI_HCD_PLATFORM || USB_OHCI_HCD_PLATFORM
+ select RESET_CONTROLLER
+ select GENERIC_PHY
+ help
+ Enable this to support the USB PHY on Atheros AR71XX/9XXX SoCs.
+
+config PHY_QCOM_APQ8064_SATA
+ tristate "Qualcomm APQ8064 SATA SerDes/PHY driver"
+ depends on ARCH_QCOM
+ depends on HAS_IOMEM
+ depends on OF
+ select GENERIC_PHY
+
+config PHY_QCOM_IPQ806X_SATA
+ tristate "Qualcomm IPQ806x SATA SerDes/PHY driver"
+ depends on ARCH_QCOM
+ depends on HAS_IOMEM
+ depends on OF
+ select GENERIC_PHY
+
+config PHY_QCOM_QMP
+ tristate "Qualcomm QMP PHY Driver"
+ depends on OF && COMMON_CLK && (ARCH_QCOM || COMPILE_TEST)
+ select GENERIC_PHY
+ help
+ Enable this to support the QMP PHY transceiver that is used
+ with controllers such as PCIe, UFS, and USB on Qualcomm chips.
+
+config PHY_QCOM_QUSB2
+ tristate "Qualcomm QUSB2 PHY Driver"
+ depends on OF && (ARCH_QCOM || COMPILE_TEST)
+ depends on NVMEM || !NVMEM
+ select GENERIC_PHY
+ help
+ Enable this to support the HighSpeed QUSB2 PHY transceiver for USB
+ controllers on Qualcomm chips. This driver supports the high-speed
+ PHY which is usually paired with either the ChipIdea or Synopsys DWC3
+ USB IPs on MSM SOCs.
+
+config PHY_QCOM_UFS
+ tristate "Qualcomm UFS PHY driver"
+ depends on OF && ARCH_QCOM
+ select GENERIC_PHY
+ help
+ Support for UFS PHY on QCOM chipsets.
+
+config PHY_QCOM_USB_HS
+ tristate "Qualcomm USB HS PHY module"
+ depends on USB_ULPI_BUS
+ depends on EXTCON || !EXTCON # if EXTCON=m, this cannot be built-in
+ select GENERIC_PHY
+ help
+ Support for the USB high-speed ULPI compliant phy on Qualcomm
+ chipsets.
+
+config PHY_QCOM_USB_HSIC
+ tristate "Qualcomm USB HSIC ULPI PHY module"
+ depends on USB_ULPI_BUS
+ select GENERIC_PHY
+ help
+ Support for the USB HSIC ULPI compliant PHY on QCOM chipsets.
diff --git a/drivers/phy/qualcomm/Makefile b/drivers/phy/qualcomm/Makefile
new file mode 100644
index 000000000..deb831f45
--- /dev/null
+++ b/drivers/phy/qualcomm/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_PHY_ATH79_USB) += phy-ath79-usb.o
+obj-$(CONFIG_PHY_QCOM_APQ8064_SATA) += phy-qcom-apq8064-sata.o
+obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o
+obj-$(CONFIG_PHY_QCOM_QMP) += phy-qcom-qmp.o
+obj-$(CONFIG_PHY_QCOM_QUSB2) += phy-qcom-qusb2.o
+obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs.o
+obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-14nm.o
+obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-20nm.o
+obj-$(CONFIG_PHY_QCOM_USB_HS) += phy-qcom-usb-hs.o
+obj-$(CONFIG_PHY_QCOM_USB_HSIC) += phy-qcom-usb-hsic.o
diff --git a/drivers/phy/qualcomm/phy-ath79-usb.c b/drivers/phy/qualcomm/phy-ath79-usb.c
new file mode 100644
index 000000000..09a77e556
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-ath79-usb.c
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Atheros AR71XX/9XXX USB PHY driver
+ *
+ * Copyright (C) 2015-2018 Alban Bedel <albeu@free.fr>
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+#include <linux/reset.h>
+
+struct ath79_usb_phy {
+ struct reset_control *reset;
+ /* The suspend override logic is inverted, hence the no prefix
+ * to make the code a bit easier to understand.
+ */
+ struct reset_control *no_suspend_override;
+};
+
+static int ath79_usb_phy_power_on(struct phy *phy)
+{
+ struct ath79_usb_phy *priv = phy_get_drvdata(phy);
+ int err = 0;
+
+ if (priv->no_suspend_override) {
+ err = reset_control_assert(priv->no_suspend_override);
+ if (err)
+ return err;
+ }
+
+ err = reset_control_deassert(priv->reset);
+ if (err && priv->no_suspend_override)
+ reset_control_deassert(priv->no_suspend_override);
+
+ return err;
+}
+
+static int ath79_usb_phy_power_off(struct phy *phy)
+{
+ struct ath79_usb_phy *priv = phy_get_drvdata(phy);
+ int err = 0;
+
+ err = reset_control_assert(priv->reset);
+ if (err)
+ return err;
+
+ if (priv->no_suspend_override) {
+ err = reset_control_deassert(priv->no_suspend_override);
+ if (err)
+ reset_control_deassert(priv->reset);
+ }
+
+ return err;
+}
+
+static const struct phy_ops ath79_usb_phy_ops = {
+ .power_on = ath79_usb_phy_power_on,
+ .power_off = ath79_usb_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int ath79_usb_phy_probe(struct platform_device *pdev)
+{
+ struct ath79_usb_phy *priv;
+ struct phy *phy;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->reset = devm_reset_control_get(&pdev->dev, "phy");
+ if (IS_ERR(priv->reset))
+ return PTR_ERR(priv->reset);
+
+ priv->no_suspend_override = devm_reset_control_get_optional(
+ &pdev->dev, "usb-suspend-override");
+ if (IS_ERR(priv->no_suspend_override))
+ return PTR_ERR(priv->no_suspend_override);
+
+ phy = devm_phy_create(&pdev->dev, NULL, &ath79_usb_phy_ops);
+ if (IS_ERR(phy))
+ return PTR_ERR(phy);
+
+ phy_set_drvdata(phy, priv);
+
+ return PTR_ERR_OR_ZERO(devm_of_phy_provider_register(
+ &pdev->dev, of_phy_simple_xlate));
+}
+
+static const struct of_device_id ath79_usb_phy_of_match[] = {
+ { .compatible = "qca,ar7100-usb-phy" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ath79_usb_phy_of_match);
+
+static struct platform_driver ath79_usb_phy_driver = {
+ .probe = ath79_usb_phy_probe,
+ .driver = {
+ .of_match_table = ath79_usb_phy_of_match,
+ .name = "ath79-usb-phy",
+ }
+};
+module_platform_driver(ath79_usb_phy_driver);
+
+MODULE_DESCRIPTION("ATH79 USB PHY driver");
+MODULE_AUTHOR("Alban Bedel <albeu@free.fr>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/qualcomm/phy-qcom-apq8064-sata.c b/drivers/phy/qualcomm/phy-qcom-apq8064-sata.c
new file mode 100644
index 000000000..c6925e3e8
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-apq8064-sata.c
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/time.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+
+/* PHY registers */
+#define UNIPHY_PLL_REFCLK_CFG 0x000
+#define UNIPHY_PLL_PWRGEN_CFG 0x014
+#define UNIPHY_PLL_GLB_CFG 0x020
+#define UNIPHY_PLL_SDM_CFG0 0x038
+#define UNIPHY_PLL_SDM_CFG1 0x03C
+#define UNIPHY_PLL_SDM_CFG2 0x040
+#define UNIPHY_PLL_SDM_CFG3 0x044
+#define UNIPHY_PLL_SDM_CFG4 0x048
+#define UNIPHY_PLL_SSC_CFG0 0x04C
+#define UNIPHY_PLL_SSC_CFG1 0x050
+#define UNIPHY_PLL_SSC_CFG2 0x054
+#define UNIPHY_PLL_SSC_CFG3 0x058
+#define UNIPHY_PLL_LKDET_CFG0 0x05C
+#define UNIPHY_PLL_LKDET_CFG1 0x060
+#define UNIPHY_PLL_LKDET_CFG2 0x064
+#define UNIPHY_PLL_CAL_CFG0 0x06C
+#define UNIPHY_PLL_CAL_CFG8 0x08C
+#define UNIPHY_PLL_CAL_CFG9 0x090
+#define UNIPHY_PLL_CAL_CFG10 0x094
+#define UNIPHY_PLL_CAL_CFG11 0x098
+#define UNIPHY_PLL_STATUS 0x0C0
+
+#define SATA_PHY_SER_CTRL 0x100
+#define SATA_PHY_TX_DRIV_CTRL0 0x104
+#define SATA_PHY_TX_DRIV_CTRL1 0x108
+#define SATA_PHY_TX_IMCAL0 0x11C
+#define SATA_PHY_TX_IMCAL2 0x124
+#define SATA_PHY_RX_IMCAL0 0x128
+#define SATA_PHY_EQUAL 0x13C
+#define SATA_PHY_OOB_TERM 0x144
+#define SATA_PHY_CDR_CTRL0 0x148
+#define SATA_PHY_CDR_CTRL1 0x14C
+#define SATA_PHY_CDR_CTRL2 0x150
+#define SATA_PHY_CDR_CTRL3 0x154
+#define SATA_PHY_PI_CTRL0 0x168
+#define SATA_PHY_POW_DWN_CTRL0 0x180
+#define SATA_PHY_POW_DWN_CTRL1 0x184
+#define SATA_PHY_TX_DATA_CTRL 0x188
+#define SATA_PHY_ALIGNP 0x1A4
+#define SATA_PHY_TX_IMCAL_STAT 0x1E4
+#define SATA_PHY_RX_IMCAL_STAT 0x1E8
+
+#define UNIPHY_PLL_LOCK BIT(0)
+#define SATA_PHY_TX_CAL BIT(0)
+#define SATA_PHY_RX_CAL BIT(0)
+
+/* default timeout set to 1 sec */
+#define TIMEOUT_MS 10000
+#define DELAY_INTERVAL_US 100
+
+struct qcom_apq8064_sata_phy {
+ void __iomem *mmio;
+ struct clk *cfg_clk;
+ struct device *dev;
+};
+
+/* Helper function to do poll and timeout */
+static int read_poll_timeout(void __iomem *addr, u32 mask)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(TIMEOUT_MS);
+
+ do {
+ if (readl_relaxed(addr) & mask)
+ return 0;
+
+ usleep_range(DELAY_INTERVAL_US, DELAY_INTERVAL_US + 50);
+ } while (!time_after(jiffies, timeout));
+
+ return (readl_relaxed(addr) & mask) ? 0 : -ETIMEDOUT;
+}
+
+static int qcom_apq8064_sata_phy_init(struct phy *generic_phy)
+{
+ struct qcom_apq8064_sata_phy *phy = phy_get_drvdata(generic_phy);
+ void __iomem *base = phy->mmio;
+ int ret = 0;
+
+ /* SATA phy initialization */
+ writel_relaxed(0x01, base + SATA_PHY_SER_CTRL);
+ writel_relaxed(0xB1, base + SATA_PHY_POW_DWN_CTRL0);
+ /* Make sure the power down happens before power up */
+ mb();
+ usleep_range(10, 60);
+
+ writel_relaxed(0x01, base + SATA_PHY_POW_DWN_CTRL0);
+ writel_relaxed(0x3E, base + SATA_PHY_POW_DWN_CTRL1);
+ writel_relaxed(0x01, base + SATA_PHY_RX_IMCAL0);
+ writel_relaxed(0x01, base + SATA_PHY_TX_IMCAL0);
+ writel_relaxed(0x02, base + SATA_PHY_TX_IMCAL2);
+
+ /* Write UNIPHYPLL registers to configure PLL */
+ writel_relaxed(0x04, base + UNIPHY_PLL_REFCLK_CFG);
+ writel_relaxed(0x00, base + UNIPHY_PLL_PWRGEN_CFG);
+
+ writel_relaxed(0x0A, base + UNIPHY_PLL_CAL_CFG0);
+ writel_relaxed(0xF3, base + UNIPHY_PLL_CAL_CFG8);
+ writel_relaxed(0x01, base + UNIPHY_PLL_CAL_CFG9);
+ writel_relaxed(0xED, base + UNIPHY_PLL_CAL_CFG10);
+ writel_relaxed(0x02, base + UNIPHY_PLL_CAL_CFG11);
+
+ writel_relaxed(0x36, base + UNIPHY_PLL_SDM_CFG0);
+ writel_relaxed(0x0D, base + UNIPHY_PLL_SDM_CFG1);
+ writel_relaxed(0xA3, base + UNIPHY_PLL_SDM_CFG2);
+ writel_relaxed(0xF0, base + UNIPHY_PLL_SDM_CFG3);
+ writel_relaxed(0x00, base + UNIPHY_PLL_SDM_CFG4);
+
+ writel_relaxed(0x19, base + UNIPHY_PLL_SSC_CFG0);
+ writel_relaxed(0xE1, base + UNIPHY_PLL_SSC_CFG1);
+ writel_relaxed(0x00, base + UNIPHY_PLL_SSC_CFG2);
+ writel_relaxed(0x11, base + UNIPHY_PLL_SSC_CFG3);
+
+ writel_relaxed(0x04, base + UNIPHY_PLL_LKDET_CFG0);
+ writel_relaxed(0xFF, base + UNIPHY_PLL_LKDET_CFG1);
+
+ writel_relaxed(0x02, base + UNIPHY_PLL_GLB_CFG);
+ /* make sure global config LDO power down happens before power up */
+ mb();
+
+ writel_relaxed(0x03, base + UNIPHY_PLL_GLB_CFG);
+ writel_relaxed(0x05, base + UNIPHY_PLL_LKDET_CFG2);
+
+ /* PLL Lock wait */
+ ret = read_poll_timeout(base + UNIPHY_PLL_STATUS, UNIPHY_PLL_LOCK);
+ if (ret) {
+ dev_err(phy->dev, "poll timeout UNIPHY_PLL_STATUS\n");
+ return ret;
+ }
+
+ /* TX Calibration */
+ ret = read_poll_timeout(base + SATA_PHY_TX_IMCAL_STAT, SATA_PHY_TX_CAL);
+ if (ret) {
+ dev_err(phy->dev, "poll timeout SATA_PHY_TX_IMCAL_STAT\n");
+ return ret;
+ }
+
+ /* RX Calibration */
+ ret = read_poll_timeout(base + SATA_PHY_RX_IMCAL_STAT, SATA_PHY_RX_CAL);
+ if (ret) {
+ dev_err(phy->dev, "poll timeout SATA_PHY_RX_IMCAL_STAT\n");
+ return ret;
+ }
+
+ /* SATA phy calibrated succesfully, power up to functional mode */
+ writel_relaxed(0x3E, base + SATA_PHY_POW_DWN_CTRL1);
+ writel_relaxed(0x01, base + SATA_PHY_RX_IMCAL0);
+ writel_relaxed(0x01, base + SATA_PHY_TX_IMCAL0);
+
+ writel_relaxed(0x00, base + SATA_PHY_POW_DWN_CTRL1);
+ writel_relaxed(0x59, base + SATA_PHY_CDR_CTRL0);
+ writel_relaxed(0x04, base + SATA_PHY_CDR_CTRL1);
+ writel_relaxed(0x00, base + SATA_PHY_CDR_CTRL2);
+ writel_relaxed(0x00, base + SATA_PHY_PI_CTRL0);
+ writel_relaxed(0x00, base + SATA_PHY_CDR_CTRL3);
+ writel_relaxed(0x01, base + SATA_PHY_POW_DWN_CTRL0);
+
+ writel_relaxed(0x11, base + SATA_PHY_TX_DATA_CTRL);
+ writel_relaxed(0x43, base + SATA_PHY_ALIGNP);
+ writel_relaxed(0x04, base + SATA_PHY_OOB_TERM);
+
+ writel_relaxed(0x01, base + SATA_PHY_EQUAL);
+ writel_relaxed(0x09, base + SATA_PHY_TX_DRIV_CTRL0);
+ writel_relaxed(0x09, base + SATA_PHY_TX_DRIV_CTRL1);
+
+ return 0;
+}
+
+static int qcom_apq8064_sata_phy_exit(struct phy *generic_phy)
+{
+ struct qcom_apq8064_sata_phy *phy = phy_get_drvdata(generic_phy);
+ void __iomem *base = phy->mmio;
+
+ /* Power down PHY */
+ writel_relaxed(0xF8, base + SATA_PHY_POW_DWN_CTRL0);
+ writel_relaxed(0xFE, base + SATA_PHY_POW_DWN_CTRL1);
+
+ /* Power down PLL block */
+ writel_relaxed(0x00, base + UNIPHY_PLL_GLB_CFG);
+
+ return 0;
+}
+
+static const struct phy_ops qcom_apq8064_sata_phy_ops = {
+ .init = qcom_apq8064_sata_phy_init,
+ .exit = qcom_apq8064_sata_phy_exit,
+ .owner = THIS_MODULE,
+};
+
+static int qcom_apq8064_sata_phy_probe(struct platform_device *pdev)
+{
+ struct qcom_apq8064_sata_phy *phy;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct phy_provider *phy_provider;
+ struct phy *generic_phy;
+ int ret;
+
+ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ phy->mmio = devm_ioremap_resource(dev, res);
+ if (IS_ERR(phy->mmio))
+ return PTR_ERR(phy->mmio);
+
+ generic_phy = devm_phy_create(dev, NULL, &qcom_apq8064_sata_phy_ops);
+ if (IS_ERR(generic_phy)) {
+ dev_err(dev, "%s: failed to create phy\n", __func__);
+ return PTR_ERR(generic_phy);
+ }
+
+ phy->dev = dev;
+ phy_set_drvdata(generic_phy, phy);
+ platform_set_drvdata(pdev, phy);
+
+ phy->cfg_clk = devm_clk_get(dev, "cfg");
+ if (IS_ERR(phy->cfg_clk)) {
+ dev_err(dev, "Failed to get sata cfg clock\n");
+ return PTR_ERR(phy->cfg_clk);
+ }
+
+ ret = clk_prepare_enable(phy->cfg_clk);
+ if (ret)
+ return ret;
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(phy_provider)) {
+ clk_disable_unprepare(phy->cfg_clk);
+ dev_err(dev, "%s: failed to register phy\n", __func__);
+ return PTR_ERR(phy_provider);
+ }
+
+ return 0;
+}
+
+static int qcom_apq8064_sata_phy_remove(struct platform_device *pdev)
+{
+ struct qcom_apq8064_sata_phy *phy = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(phy->cfg_clk);
+
+ return 0;
+}
+
+static const struct of_device_id qcom_apq8064_sata_phy_of_match[] = {
+ { .compatible = "qcom,apq8064-sata-phy" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, qcom_apq8064_sata_phy_of_match);
+
+static struct platform_driver qcom_apq8064_sata_phy_driver = {
+ .probe = qcom_apq8064_sata_phy_probe,
+ .remove = qcom_apq8064_sata_phy_remove,
+ .driver = {
+ .name = "qcom-apq8064-sata-phy",
+ .of_match_table = qcom_apq8064_sata_phy_of_match,
+ }
+};
+module_platform_driver(qcom_apq8064_sata_phy_driver);
+
+MODULE_DESCRIPTION("QCOM apq8064 SATA PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/qualcomm/phy-qcom-ipq806x-sata.c b/drivers/phy/qualcomm/phy-qcom-ipq806x-sata.c
new file mode 100644
index 000000000..0ad127cc9
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-ipq806x-sata.c
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/time.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+
+struct qcom_ipq806x_sata_phy {
+ void __iomem *mmio;
+ struct clk *cfg_clk;
+ struct device *dev;
+};
+
+#define __set(v, a, b) (((v) << (b)) & GENMASK(a, b))
+
+#define SATA_PHY_P0_PARAM0 0x200
+#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3(x) __set(x, 17, 12)
+#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3_MASK GENMASK(17, 12)
+#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN2(x) __set(x, 11, 6)
+#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN2_MASK GENMASK(11, 6)
+#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN1(x) __set(x, 5, 0)
+#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN1_MASK GENMASK(5, 0)
+
+#define SATA_PHY_P0_PARAM1 0x204
+#define SATA_PHY_P0_PARAM1_RESERVED_BITS31_21(x) __set(x, 31, 21)
+#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3(x) __set(x, 20, 14)
+#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3_MASK GENMASK(20, 14)
+#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2(x) __set(x, 13, 7)
+#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2_MASK GENMASK(13, 7)
+#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1(x) __set(x, 6, 0)
+#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1_MASK GENMASK(6, 0)
+
+#define SATA_PHY_P0_PARAM2 0x208
+#define SATA_PHY_P0_PARAM2_RX_EQ(x) __set(x, 20, 18)
+#define SATA_PHY_P0_PARAM2_RX_EQ_MASK GENMASK(20, 18)
+
+#define SATA_PHY_P0_PARAM3 0x20C
+#define SATA_PHY_SSC_EN 0x8
+#define SATA_PHY_P0_PARAM4 0x210
+#define SATA_PHY_REF_SSP_EN 0x2
+#define SATA_PHY_RESET 0x1
+
+static int qcom_ipq806x_sata_phy_init(struct phy *generic_phy)
+{
+ struct qcom_ipq806x_sata_phy *phy = phy_get_drvdata(generic_phy);
+ u32 reg;
+
+ /* Setting SSC_EN to 1 */
+ reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM3);
+ reg = reg | SATA_PHY_SSC_EN;
+ writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM3);
+
+ reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM0) &
+ ~(SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3_MASK |
+ SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN2_MASK |
+ SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN1_MASK);
+ reg |= SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3(0xf);
+ writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM0);
+
+ reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM1) &
+ ~(SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3_MASK |
+ SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2_MASK |
+ SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1_MASK);
+ reg |= SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3(0x55) |
+ SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2(0x55) |
+ SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1(0x55);
+ writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM1);
+
+ reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM2) &
+ ~SATA_PHY_P0_PARAM2_RX_EQ_MASK;
+ reg |= SATA_PHY_P0_PARAM2_RX_EQ(0x3);
+ writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM2);
+
+ /* Setting PHY_RESET to 1 */
+ reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4);
+ reg = reg | SATA_PHY_RESET;
+ writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4);
+
+ /* Setting REF_SSP_EN to 1 */
+ reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4);
+ reg = reg | SATA_PHY_REF_SSP_EN | SATA_PHY_RESET;
+ writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4);
+
+ /* make sure all changes complete before we let the PHY out of reset */
+ mb();
+
+ /* sleep for max. 50us more to combine processor wakeups */
+ usleep_range(20, 20 + 50);
+
+ /* Clearing PHY_RESET to 0 */
+ reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4);
+ reg = reg & ~SATA_PHY_RESET;
+ writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4);
+
+ return 0;
+}
+
+static int qcom_ipq806x_sata_phy_exit(struct phy *generic_phy)
+{
+ struct qcom_ipq806x_sata_phy *phy = phy_get_drvdata(generic_phy);
+ u32 reg;
+
+ /* Setting PHY_RESET to 1 */
+ reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4);
+ reg = reg | SATA_PHY_RESET;
+ writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4);
+
+ return 0;
+}
+
+static const struct phy_ops qcom_ipq806x_sata_phy_ops = {
+ .init = qcom_ipq806x_sata_phy_init,
+ .exit = qcom_ipq806x_sata_phy_exit,
+ .owner = THIS_MODULE,
+};
+
+static int qcom_ipq806x_sata_phy_probe(struct platform_device *pdev)
+{
+ struct qcom_ipq806x_sata_phy *phy;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct phy_provider *phy_provider;
+ struct phy *generic_phy;
+ int ret;
+
+ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ phy->mmio = devm_ioremap_resource(dev, res);
+ if (IS_ERR(phy->mmio))
+ return PTR_ERR(phy->mmio);
+
+ generic_phy = devm_phy_create(dev, NULL, &qcom_ipq806x_sata_phy_ops);
+ if (IS_ERR(generic_phy)) {
+ dev_err(dev, "%s: failed to create phy\n", __func__);
+ return PTR_ERR(generic_phy);
+ }
+
+ phy->dev = dev;
+ phy_set_drvdata(generic_phy, phy);
+ platform_set_drvdata(pdev, phy);
+
+ phy->cfg_clk = devm_clk_get(dev, "cfg");
+ if (IS_ERR(phy->cfg_clk)) {
+ dev_err(dev, "Failed to get sata cfg clock\n");
+ return PTR_ERR(phy->cfg_clk);
+ }
+
+ ret = clk_prepare_enable(phy->cfg_clk);
+ if (ret)
+ return ret;
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(phy_provider)) {
+ clk_disable_unprepare(phy->cfg_clk);
+ dev_err(dev, "%s: failed to register phy\n", __func__);
+ return PTR_ERR(phy_provider);
+ }
+
+ return 0;
+}
+
+static int qcom_ipq806x_sata_phy_remove(struct platform_device *pdev)
+{
+ struct qcom_ipq806x_sata_phy *phy = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(phy->cfg_clk);
+
+ return 0;
+}
+
+static const struct of_device_id qcom_ipq806x_sata_phy_of_match[] = {
+ { .compatible = "qcom,ipq806x-sata-phy" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, qcom_ipq806x_sata_phy_of_match);
+
+static struct platform_driver qcom_ipq806x_sata_phy_driver = {
+ .probe = qcom_ipq806x_sata_phy_probe,
+ .remove = qcom_ipq806x_sata_phy_remove,
+ .driver = {
+ .name = "qcom-ipq806x-sata-phy",
+ .of_match_table = qcom_ipq806x_sata_phy_of_match,
+ }
+};
+module_platform_driver(qcom_ipq806x_sata_phy_driver);
+
+MODULE_DESCRIPTION("QCOM IPQ806x SATA PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c
new file mode 100644
index 000000000..c64262f4f
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-qmp.c
@@ -0,0 +1,1668 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/phy/phy.h>
+
+#include "phy-qcom-qmp.h"
+
+/* QPHY_SW_RESET bit */
+#define SW_RESET BIT(0)
+/* QPHY_POWER_DOWN_CONTROL */
+#define SW_PWRDN BIT(0)
+#define REFCLK_DRV_DSBL BIT(1)
+/* QPHY_START_CONTROL bits */
+#define SERDES_START BIT(0)
+#define PCS_START BIT(1)
+#define PLL_READY_GATE_EN BIT(3)
+/* QPHY_PCS_STATUS bit */
+#define PHYSTATUS BIT(6)
+/* QPHY_COM_PCS_READY_STATUS bit */
+#define PCS_READY BIT(0)
+
+/* QPHY_V3_DP_COM_RESET_OVRD_CTRL register bits */
+/* DP PHY soft reset */
+#define SW_DPPHY_RESET BIT(0)
+/* mux to select DP PHY reset control, 0:HW control, 1: software reset */
+#define SW_DPPHY_RESET_MUX BIT(1)
+/* USB3 PHY soft reset */
+#define SW_USB3PHY_RESET BIT(2)
+/* mux to select USB3 PHY reset control, 0:HW control, 1: software reset */
+#define SW_USB3PHY_RESET_MUX BIT(3)
+
+/* QPHY_V3_DP_COM_PHY_MODE_CTRL register bits */
+#define USB3_MODE BIT(0) /* enables USB3 mode */
+#define DP_MODE BIT(1) /* enables DP mode */
+
+/* QPHY_PCS_AUTONOMOUS_MODE_CTRL register bits */
+#define ARCVR_DTCT_EN BIT(0)
+#define ALFPS_DTCT_EN BIT(1)
+#define ARCVR_DTCT_EVENT_SEL BIT(4)
+
+/* QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR register bits */
+#define IRQ_CLEAR BIT(0)
+
+/* QPHY_PCS_LFPS_RXTERM_IRQ_STATUS register bits */
+#define RCVR_DETECT BIT(0)
+
+/* QPHY_V3_PCS_MISC_CLAMP_ENABLE register bits */
+#define CLAMP_EN BIT(0) /* enables i/o clamp_n */
+
+#define PHY_INIT_COMPLETE_TIMEOUT 10000
+#define POWER_DOWN_DELAY_US_MIN 10
+#define POWER_DOWN_DELAY_US_MAX 11
+
+#define MAX_PROP_NAME 32
+
+struct qmp_phy_init_tbl {
+ unsigned int offset;
+ unsigned int val;
+ /*
+ * register part of layout ?
+ * if yes, then offset gives index in the reg-layout
+ */
+ int in_layout;
+};
+
+#define QMP_PHY_INIT_CFG(o, v) \
+ { \
+ .offset = o, \
+ .val = v, \
+ }
+
+#define QMP_PHY_INIT_CFG_L(o, v) \
+ { \
+ .offset = o, \
+ .val = v, \
+ .in_layout = 1, \
+ }
+
+/* set of registers with offsets different per-PHY */
+enum qphy_reg_layout {
+ /* Common block control registers */
+ QPHY_COM_SW_RESET,
+ QPHY_COM_POWER_DOWN_CONTROL,
+ QPHY_COM_START_CONTROL,
+ QPHY_COM_PCS_READY_STATUS,
+ /* PCS registers */
+ QPHY_PLL_LOCK_CHK_DLY_TIME,
+ QPHY_FLL_CNTRL1,
+ QPHY_FLL_CNTRL2,
+ QPHY_FLL_CNT_VAL_L,
+ QPHY_FLL_CNT_VAL_H_TOL,
+ QPHY_FLL_MAN_CODE,
+ QPHY_SW_RESET,
+ QPHY_START_CTRL,
+ QPHY_PCS_READY_STATUS,
+ QPHY_PCS_AUTONOMOUS_MODE_CTRL,
+ QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR,
+ QPHY_PCS_LFPS_RXTERM_IRQ_STATUS,
+};
+
+static const unsigned int pciephy_regs_layout[] = {
+ [QPHY_COM_SW_RESET] = 0x400,
+ [QPHY_COM_POWER_DOWN_CONTROL] = 0x404,
+ [QPHY_COM_START_CONTROL] = 0x408,
+ [QPHY_COM_PCS_READY_STATUS] = 0x448,
+ [QPHY_PLL_LOCK_CHK_DLY_TIME] = 0xa8,
+ [QPHY_FLL_CNTRL1] = 0xc4,
+ [QPHY_FLL_CNTRL2] = 0xc8,
+ [QPHY_FLL_CNT_VAL_L] = 0xcc,
+ [QPHY_FLL_CNT_VAL_H_TOL] = 0xd0,
+ [QPHY_FLL_MAN_CODE] = 0xd4,
+ [QPHY_SW_RESET] = 0x00,
+ [QPHY_START_CTRL] = 0x08,
+ [QPHY_PCS_READY_STATUS] = 0x174,
+};
+
+static const unsigned int usb3phy_regs_layout[] = {
+ [QPHY_FLL_CNTRL1] = 0xc0,
+ [QPHY_FLL_CNTRL2] = 0xc4,
+ [QPHY_FLL_CNT_VAL_L] = 0xc8,
+ [QPHY_FLL_CNT_VAL_H_TOL] = 0xcc,
+ [QPHY_FLL_MAN_CODE] = 0xd0,
+ [QPHY_SW_RESET] = 0x00,
+ [QPHY_START_CTRL] = 0x08,
+ [QPHY_PCS_READY_STATUS] = 0x17c,
+ [QPHY_PCS_AUTONOMOUS_MODE_CTRL] = 0x0d4,
+ [QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR] = 0x0d8,
+ [QPHY_PCS_LFPS_RXTERM_IRQ_STATUS] = 0x178,
+};
+
+static const unsigned int qmp_v3_usb3phy_regs_layout[] = {
+ [QPHY_SW_RESET] = 0x00,
+ [QPHY_START_CTRL] = 0x08,
+ [QPHY_PCS_READY_STATUS] = 0x174,
+ [QPHY_PCS_AUTONOMOUS_MODE_CTRL] = 0x0d8,
+ [QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR] = 0x0dc,
+ [QPHY_PCS_LFPS_RXTERM_IRQ_STATUS] = 0x170,
+};
+
+static const struct qmp_phy_init_tbl msm8996_pcie_serdes_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x1c),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CLK_ENABLE1, 0x10),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x33),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CMN_CONFIG, 0x06),
+ QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_EN, 0x42),
+ QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_MAP, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_TIMER1, 0xff),
+ QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_TIMER2, 0x1f),
+ QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SVS_MODE_CLK_SEL, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CORE_CLK_EN, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CORECLK_DIV, 0x0a),
+ QMP_PHY_INIT_CFG(QSERDES_COM_BG_TIMER, 0x09),
+ QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE0, 0x82),
+ QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x03),
+ QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x55),
+ QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x55),
+ QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE0, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE0, 0x1a),
+ QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE0, 0x0a),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x33),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SYS_CLK_CTRL, 0x02),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_BUF_ENABLE, 0x1f),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x04),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE0, 0x0b),
+ QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
+ QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE0, 0x28),
+ QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_EN_CENTER, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER1, 0x31),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER2, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER1, 0x02),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER2, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE1, 0x2f),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE2, 0x19),
+ QMP_PHY_INIT_CFG(QSERDES_COM_RESCODE_DIV_NUM, 0x15),
+ QMP_PHY_INIT_CFG(QSERDES_COM_BG_TRIM, 0x0f),
+ QMP_PHY_INIT_CFG(QSERDES_COM_PLL_IVCO, 0x0f),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CLK_EP_DIV, 0x19),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CLK_ENABLE1, 0x10),
+ QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_COM_RESCODE_DIV_NUM, 0x40),
+};
+
+static const struct qmp_phy_init_tbl msm8996_pcie_tx_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x45),
+ QMP_PHY_INIT_CFG(QSERDES_TX_LANE_MODE, 0x06),
+};
+
+static const struct qmp_phy_init_tbl msm8996_pcie_rx_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_ENABLES, 0x1c),
+ QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4, 0xdb),
+ QMP_PHY_INIT_CFG(QSERDES_RX_RX_BAND, 0x18),
+ QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN, 0x04),
+ QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN_HALF, 0x04),
+ QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x4b),
+ QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x14),
+ QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_LVL, 0x19),
+};
+
+static const struct qmp_phy_init_tbl msm8996_pcie_pcs_tbl[] = {
+ QMP_PHY_INIT_CFG(QPHY_RX_IDLE_DTCT_CNTRL, 0x4c),
+ QMP_PHY_INIT_CFG(QPHY_PWRUP_RESET_DLY_TIME_AUXCLK, 0x00),
+ QMP_PHY_INIT_CFG(QPHY_LP_WAKEUP_DLY_TIME_AUXCLK, 0x01),
+
+ QMP_PHY_INIT_CFG_L(QPHY_PLL_LOCK_CHK_DLY_TIME, 0x05),
+
+ QMP_PHY_INIT_CFG(QPHY_ENDPOINT_REFCLK_DRIVE, 0x05),
+ QMP_PHY_INIT_CFG(QPHY_POWER_DOWN_CONTROL, 0x02),
+ QMP_PHY_INIT_CFG(QPHY_POWER_STATE_CONFIG4, 0x00),
+ QMP_PHY_INIT_CFG(QPHY_POWER_STATE_CONFIG1, 0xa3),
+ QMP_PHY_INIT_CFG(QPHY_TXDEEMPH_M3P5DB_V0, 0x0e),
+};
+
+static const struct qmp_phy_init_tbl msm8996_usb3_serdes_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x14),
+ QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x30),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CMN_CONFIG, 0x06),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SVS_MODE_CLK_SEL, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_COM_BG_TRIM, 0x0f),
+ QMP_PHY_INIT_CFG(QSERDES_COM_PLL_IVCO, 0x0f),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SYS_CLK_CTRL, 0x04),
+ /* PLL and Loop filter settings */
+ QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE0, 0x82),
+ QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x55),
+ QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x55),
+ QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x03),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE0, 0x0b),
+ QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
+ QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE0, 0x28),
+ QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80),
+ QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_CTRL, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE0, 0x15),
+ QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE0, 0x34),
+ QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE0, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CORE_CLK_EN, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_CFG, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_MAP, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_COM_BG_TIMER, 0x0a),
+ /* SSC settings */
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_EN_CENTER, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER1, 0x31),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER2, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER1, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER2, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE1, 0xde),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE2, 0x07),
+};
+
+static const struct qmp_phy_init_tbl msm8996_usb3_tx_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x45),
+ QMP_PHY_INIT_CFG(QSERDES_TX_RCV_DETECT_LVL_2, 0x12),
+ QMP_PHY_INIT_CFG(QSERDES_TX_LANE_MODE, 0x06),
+};
+
+static const struct qmp_phy_init_tbl msm8996_usb3_rx_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_FASTLOCK_FO_GAIN, 0x0b),
+ QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN, 0x04),
+ QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x02),
+ QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3, 0x4c),
+ QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4, 0xbb),
+ QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x77),
+ QMP_PHY_INIT_CFG(QSERDES_RX_RX_OFFSET_ADAPTOR_CNTRL2, 0x80),
+ QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_CNTRL, 0x03),
+ QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_LVL, 0x18),
+ QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x16),
+};
+
+static const struct qmp_phy_init_tbl msm8996_usb3_pcs_tbl[] = {
+ /* FLL settings */
+ QMP_PHY_INIT_CFG_L(QPHY_FLL_CNTRL2, 0x03),
+ QMP_PHY_INIT_CFG_L(QPHY_FLL_CNTRL1, 0x02),
+ QMP_PHY_INIT_CFG_L(QPHY_FLL_CNT_VAL_L, 0x09),
+ QMP_PHY_INIT_CFG_L(QPHY_FLL_CNT_VAL_H_TOL, 0x42),
+ QMP_PHY_INIT_CFG_L(QPHY_FLL_MAN_CODE, 0x85),
+
+ /* Lock Det settings */
+ QMP_PHY_INIT_CFG(QPHY_LOCK_DETECT_CONFIG1, 0xd1),
+ QMP_PHY_INIT_CFG(QPHY_LOCK_DETECT_CONFIG2, 0x1f),
+ QMP_PHY_INIT_CFG(QPHY_LOCK_DETECT_CONFIG3, 0x47),
+ QMP_PHY_INIT_CFG(QPHY_POWER_STATE_CONFIG2, 0x08),
+};
+
+static const struct qmp_phy_init_tbl ipq8074_pcie_serdes_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x18),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CLK_ENABLE1, 0x10),
+ QMP_PHY_INIT_CFG(QSERDES_COM_BG_TRIM, 0xf),
+ QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_EN, 0x1),
+ QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_MAP, 0x0),
+ QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_TIMER1, 0xff),
+ QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_TIMER2, 0x1f),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CMN_CONFIG, 0x6),
+ QMP_PHY_INIT_CFG(QSERDES_COM_PLL_IVCO, 0xf),
+ QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x0),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SVS_MODE_CLK_SEL, 0x1),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CORE_CLK_EN, 0x20),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CORECLK_DIV, 0xa),
+ QMP_PHY_INIT_CFG(QSERDES_COM_RESETSM_CNTRL, 0x20),
+ QMP_PHY_INIT_CFG(QSERDES_COM_BG_TIMER, 0xa),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0xa),
+ QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE0, 0x82),
+ QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x3),
+ QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x55),
+ QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x55),
+ QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE0, 0x0),
+ QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE0, 0xD),
+ QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE0, 0xD04),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x33),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SYS_CLK_CTRL, 0x2),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_BUF_ENABLE, 0x1f),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE0, 0xb),
+ QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
+ QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE0, 0x28),
+ QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x0),
+ QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80),
+ QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CTRL_BY_PSM, 0x1),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_EN_CENTER, 0x1),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER1, 0x31),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER2, 0x1),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER1, 0x2),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER2, 0x0),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE1, 0x2f),
+ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE2, 0x19),
+ QMP_PHY_INIT_CFG(QSERDES_COM_CLK_EP_DIV, 0x19),
+};
+
+static const struct qmp_phy_init_tbl ipq8074_pcie_tx_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x45),
+ QMP_PHY_INIT_CFG(QSERDES_TX_LANE_MODE, 0x6),
+ QMP_PHY_INIT_CFG(QSERDES_TX_RES_CODE_LANE_OFFSET, 0x2),
+ QMP_PHY_INIT_CFG(QSERDES_TX_RCV_DETECT_LVL_2, 0x12),
+ QMP_PHY_INIT_CFG(QSERDES_TX_EMP_POST1_LVL, 0x36),
+ QMP_PHY_INIT_CFG(QSERDES_TX_SLEW_CNTL, 0x0a),
+};
+
+static const struct qmp_phy_init_tbl ipq8074_pcie_rx_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_ENABLES, 0x1c),
+ QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x14),
+ QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x1),
+ QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3, 0x0),
+ QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4, 0xdb),
+ QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x4b),
+ QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN, 0x4),
+};
+
+static const struct qmp_phy_init_tbl ipq8074_pcie_pcs_tbl[] = {
+ QMP_PHY_INIT_CFG(QPHY_ENDPOINT_REFCLK_DRIVE, 0x4),
+ QMP_PHY_INIT_CFG(QPHY_OSC_DTCT_ACTIONS, 0x0),
+ QMP_PHY_INIT_CFG(QPHY_PWRUP_RESET_DLY_TIME_AUXCLK, 0x40),
+ QMP_PHY_INIT_CFG(QPHY_L1SS_WAKEUP_DLY_TIME_AUXCLK_MSB, 0x0),
+ QMP_PHY_INIT_CFG(QPHY_L1SS_WAKEUP_DLY_TIME_AUXCLK_LSB, 0x40),
+ QMP_PHY_INIT_CFG(QPHY_PLL_LOCK_CHK_DLY_TIME_AUXCLK_LSB, 0x0),
+ QMP_PHY_INIT_CFG(QPHY_LP_WAKEUP_DLY_TIME_AUXCLK, 0x40),
+ QMP_PHY_INIT_CFG_L(QPHY_PLL_LOCK_CHK_DLY_TIME, 0x73),
+ QMP_PHY_INIT_CFG(QPHY_RX_SIGDET_LVL, 0x99),
+ QMP_PHY_INIT_CFG(QPHY_TXDEEMPH_M6DB_V0, 0x15),
+ QMP_PHY_INIT_CFG(QPHY_TXDEEMPH_M3P5DB_V0, 0xe),
+ QMP_PHY_INIT_CFG_L(QPHY_SW_RESET, 0x0),
+ QMP_PHY_INIT_CFG_L(QPHY_START_CTRL, 0x3),
+};
+
+static const struct qmp_phy_init_tbl qmp_v3_usb3_serdes_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_IVCO, 0x07),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYSCLK_EN_SEL, 0x14),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_BIAS_EN_CLKBUFLR_EN, 0x08),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_SELECT, 0x30),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYS_CLK_CTRL, 0x02),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_RESETSM_CNTRL2, 0x08),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CMN_CONFIG, 0x16),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SVS_MODE_CLK_SEL, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_HSCLK_SEL, 0x80),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_DEC_START_MODE0, 0x82),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START1_MODE0, 0xab),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START2_MODE0, 0xea),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START3_MODE0, 0x02),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CP_CTRL_MODE0, 0x06),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_RCTRL_MODE0, 0x16),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_CCTRL_MODE0, 0x36),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN1_MODE0, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN0_MODE0, 0x3f),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE2_MODE0, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE1_MODE0, 0xc9),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CORECLK_DIV_MODE0, 0x0a),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP3_MODE0, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP2_MODE0, 0x34),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP1_MODE0, 0x15),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP_EN, 0x04),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CORE_CLK_EN, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP_CFG, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_MAP, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYSCLK_BUF_ENABLE, 0x0a),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_EN_CENTER, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_PER1, 0x31),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_PER2, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_ADJ_PER1, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_ADJ_PER2, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_STEP_SIZE1, 0x85),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_STEP_SIZE2, 0x07),
+};
+
+static const struct qmp_phy_init_tbl qmp_v3_usb3_tx_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_V3_TX_HIGHZ_DRVR_EN, 0x10),
+ QMP_PHY_INIT_CFG(QSERDES_V3_TX_RCV_DETECT_LVL_2, 0x12),
+ QMP_PHY_INIT_CFG(QSERDES_V3_TX_LANE_MODE_1, 0x16),
+ QMP_PHY_INIT_CFG(QSERDES_V3_TX_RES_CODE_LANE_OFFSET_RX, 0x09),
+ QMP_PHY_INIT_CFG(QSERDES_V3_TX_RES_CODE_LANE_OFFSET_TX, 0x06),
+};
+
+static const struct qmp_phy_init_tbl qmp_v3_usb3_rx_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_FASTLOCK_FO_GAIN, 0x0b),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0f),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL3, 0x4e),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL4, 0x18),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x77),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_OFFSET_ADAPTOR_CNTRL2, 0x80),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_CNTRL, 0x03),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_DEGLITCH_CNTRL, 0x16),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x75),
+};
+
+static const struct qmp_phy_init_tbl qmp_v3_usb3_pcs_tbl[] = {
+ /* FLL settings */
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNTRL2, 0x83),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNT_VAL_L, 0x09),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNT_VAL_H_TOL, 0xa2),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_MAN_CODE, 0x40),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNTRL1, 0x02),
+
+ /* Lock Det settings */
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_LOCK_DETECT_CONFIG1, 0xd1),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_LOCK_DETECT_CONFIG2, 0x1f),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_LOCK_DETECT_CONFIG3, 0x47),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_POWER_STATE_CONFIG2, 0x1b),
+
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_RX_SIGDET_LVL, 0xba),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V0, 0x9f),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V1, 0x9f),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V2, 0xb7),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V3, 0x4e),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V4, 0x65),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_LS, 0x6b),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V0, 0x15),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V0, 0x0d),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V1, 0x15),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V1, 0x0d),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V2, 0x15),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V2, 0x0d),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V3, 0x15),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V3, 0x1d),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V4, 0x15),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V4, 0x0d),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_LS, 0x15),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_LS, 0x0d),
+
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_RATE_SLEW_CNTRL, 0x02),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK, 0x04),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TSYNC_RSYNC_TIME, 0x44),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK, 0x04),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_P1U2_L, 0xe7),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_P1U2_H, 0x03),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_U3_L, 0x40),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_U3_H, 0x00),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_RXEQTRAINING_WAIT_TIME, 0x75),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_LFPS_TX_ECSTART_EQTLOCK, 0x86),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_RXEQTRAINING_RUN_TIME, 0x13),
+};
+
+static const struct qmp_phy_init_tbl qmp_v3_usb3_uniphy_serdes_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_IVCO, 0x07),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYSCLK_EN_SEL, 0x14),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_BIAS_EN_CLKBUFLR_EN, 0x04),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_SELECT, 0x30),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYS_CLK_CTRL, 0x02),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_RESETSM_CNTRL2, 0x08),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CMN_CONFIG, 0x06),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SVS_MODE_CLK_SEL, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_HSCLK_SEL, 0x80),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_DEC_START_MODE0, 0x82),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START1_MODE0, 0xab),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START2_MODE0, 0xea),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START3_MODE0, 0x02),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CP_CTRL_MODE0, 0x06),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_RCTRL_MODE0, 0x16),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_CCTRL_MODE0, 0x36),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN1_MODE0, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN0_MODE0, 0x3f),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE2_MODE0, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE1_MODE0, 0xc9),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CORECLK_DIV_MODE0, 0x0a),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP3_MODE0, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP2_MODE0, 0x34),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP1_MODE0, 0x15),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP_EN, 0x04),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CORE_CLK_EN, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP_CFG, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_MAP, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYSCLK_BUF_ENABLE, 0x0a),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_EN_CENTER, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_PER1, 0x31),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_PER2, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_ADJ_PER1, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_ADJ_PER2, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_STEP_SIZE1, 0x85),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_STEP_SIZE2, 0x07),
+};
+
+static const struct qmp_phy_init_tbl qmp_v3_usb3_uniphy_tx_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_V3_TX_HIGHZ_DRVR_EN, 0x10),
+ QMP_PHY_INIT_CFG(QSERDES_V3_TX_RCV_DETECT_LVL_2, 0x12),
+ QMP_PHY_INIT_CFG(QSERDES_V3_TX_LANE_MODE_1, 0xc6),
+ QMP_PHY_INIT_CFG(QSERDES_V3_TX_RES_CODE_LANE_OFFSET_RX, 0x06),
+ QMP_PHY_INIT_CFG(QSERDES_V3_TX_RES_CODE_LANE_OFFSET_TX, 0x06),
+};
+
+static const struct qmp_phy_init_tbl qmp_v3_usb3_uniphy_rx_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_VGA_CAL_CNTRL2, 0x0c),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_MODE_00, 0x50),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_FASTLOCK_FO_GAIN, 0x0b),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0e),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL3, 0x4e),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL4, 0x18),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x77),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_OFFSET_ADAPTOR_CNTRL2, 0x80),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_CNTRL, 0x03),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_DEGLITCH_CNTRL, 0x1c),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x75),
+};
+
+static const struct qmp_phy_init_tbl qmp_v3_usb3_uniphy_pcs_tbl[] = {
+ /* FLL settings */
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNTRL2, 0x83),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNT_VAL_L, 0x09),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNT_VAL_H_TOL, 0xa2),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_MAN_CODE, 0x40),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNTRL1, 0x02),
+
+ /* Lock Det settings */
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_LOCK_DETECT_CONFIG1, 0xd1),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_LOCK_DETECT_CONFIG2, 0x1f),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_LOCK_DETECT_CONFIG3, 0x47),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_POWER_STATE_CONFIG2, 0x1b),
+
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_RX_SIGDET_LVL, 0xba),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V0, 0x9f),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V1, 0x9f),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V2, 0xb5),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V3, 0x4c),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V4, 0x64),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_LS, 0x6a),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V0, 0x15),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V0, 0x0d),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V1, 0x15),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V1, 0x0d),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V2, 0x15),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V2, 0x0d),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V3, 0x15),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V3, 0x1d),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V4, 0x15),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V4, 0x0d),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_LS, 0x15),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_LS, 0x0d),
+
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_RATE_SLEW_CNTRL, 0x02),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK, 0x04),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TSYNC_RSYNC_TIME, 0x44),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK, 0x04),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_P1U2_L, 0xe7),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_P1U2_H, 0x03),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_U3_L, 0x40),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_U3_H, 0x00),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_RXEQTRAINING_WAIT_TIME, 0x75),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_LFPS_TX_ECSTART_EQTLOCK, 0x86),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_RXEQTRAINING_RUN_TIME, 0x13),
+
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_REFGEN_REQ_CONFIG1, 0x21),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_REFGEN_REQ_CONFIG2, 0x60),
+};
+
+
+/* struct qmp_phy_cfg - per-PHY initialization config */
+struct qmp_phy_cfg {
+ /* phy-type - PCIE/UFS/USB */
+ unsigned int type;
+ /* number of lanes provided by phy */
+ int nlanes;
+
+ /* Init sequence for PHY blocks - serdes, tx, rx, pcs */
+ const struct qmp_phy_init_tbl *serdes_tbl;
+ int serdes_tbl_num;
+ const struct qmp_phy_init_tbl *tx_tbl;
+ int tx_tbl_num;
+ const struct qmp_phy_init_tbl *rx_tbl;
+ int rx_tbl_num;
+ const struct qmp_phy_init_tbl *pcs_tbl;
+ int pcs_tbl_num;
+
+ /* clock ids to be requested */
+ const char * const *clk_list;
+ int num_clks;
+ /* resets to be requested */
+ const char * const *reset_list;
+ int num_resets;
+ /* regulators to be requested */
+ const char * const *vreg_list;
+ int num_vregs;
+
+ /* array of registers with different offsets */
+ const unsigned int *regs;
+
+ unsigned int start_ctrl;
+ unsigned int pwrdn_ctrl;
+ unsigned int mask_pcs_ready;
+ unsigned int mask_com_pcs_ready;
+
+ /* true, if PHY has a separate PHY_COM control block */
+ bool has_phy_com_ctrl;
+ /* true, if PHY has a reset for individual lanes */
+ bool has_lane_rst;
+ /* true, if PHY needs delay after POWER_DOWN */
+ bool has_pwrdn_delay;
+ /* power_down delay in usec */
+ int pwrdn_delay_min;
+ int pwrdn_delay_max;
+
+ /* true, if PHY has a separate DP_COM control block */
+ bool has_phy_dp_com_ctrl;
+ /* Register offset of secondary tx/rx lanes for USB DP combo PHY */
+ unsigned int tx_b_lane_offset;
+ unsigned int rx_b_lane_offset;
+};
+
+/**
+ * struct qmp_phy - per-lane phy descriptor
+ *
+ * @phy: generic phy
+ * @tx: iomapped memory space for lane's tx
+ * @rx: iomapped memory space for lane's rx
+ * @pcs: iomapped memory space for lane's pcs
+ * @pcs_misc: iomapped memory space for lane's pcs_misc
+ * @pipe_clk: pipe lock
+ * @index: lane index
+ * @qmp: QMP phy to which this lane belongs
+ * @lane_rst: lane's reset controller
+ */
+struct qmp_phy {
+ struct phy *phy;
+ void __iomem *tx;
+ void __iomem *rx;
+ void __iomem *pcs;
+ void __iomem *pcs_misc;
+ struct clk *pipe_clk;
+ unsigned int index;
+ struct qcom_qmp *qmp;
+ struct reset_control *lane_rst;
+};
+
+/**
+ * struct qcom_qmp - structure holding QMP phy block attributes
+ *
+ * @dev: device
+ * @serdes: iomapped memory space for phy's serdes
+ * @dp_com: iomapped memory space for phy's dp_com control block
+ *
+ * @clks: array of clocks required by phy
+ * @resets: array of resets required by phy
+ * @vregs: regulator supplies bulk data
+ *
+ * @cfg: phy specific configuration
+ * @phys: array of per-lane phy descriptors
+ * @phy_mutex: mutex lock for PHY common block initialization
+ * @init_count: phy common block initialization count
+ * @phy_initialized: indicate if PHY has been initialized
+ * @mode: current PHY mode
+ */
+struct qcom_qmp {
+ struct device *dev;
+ void __iomem *serdes;
+ void __iomem *dp_com;
+
+ struct clk_bulk_data *clks;
+ struct reset_control **resets;
+ struct regulator_bulk_data *vregs;
+
+ const struct qmp_phy_cfg *cfg;
+ struct qmp_phy **phys;
+
+ struct mutex phy_mutex;
+ int init_count;
+ bool phy_initialized;
+ enum phy_mode mode;
+};
+
+static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val)
+{
+ u32 reg;
+
+ reg = readl(base + offset);
+ reg |= val;
+ writel(reg, base + offset);
+
+ /* ensure that above write is through */
+ readl(base + offset);
+}
+
+static inline void qphy_clrbits(void __iomem *base, u32 offset, u32 val)
+{
+ u32 reg;
+
+ reg = readl(base + offset);
+ reg &= ~val;
+ writel(reg, base + offset);
+
+ /* ensure that above write is through */
+ readl(base + offset);
+}
+
+/* list of clocks required by phy */
+static const char * const msm8996_phy_clk_l[] = {
+ "aux", "cfg_ahb", "ref",
+};
+
+static const char * const qmp_v3_phy_clk_l[] = {
+ "aux", "cfg_ahb", "ref", "com_aux",
+};
+
+/* list of resets */
+static const char * const msm8996_pciephy_reset_l[] = {
+ "phy", "common", "cfg",
+};
+
+static const char * const msm8996_usb3phy_reset_l[] = {
+ "phy", "common",
+};
+
+/* list of regulators */
+static const char * const msm8996_phy_vreg_l[] = {
+ "vdda-phy", "vdda-pll",
+};
+
+static const struct qmp_phy_cfg msm8996_pciephy_cfg = {
+ .type = PHY_TYPE_PCIE,
+ .nlanes = 3,
+
+ .serdes_tbl = msm8996_pcie_serdes_tbl,
+ .serdes_tbl_num = ARRAY_SIZE(msm8996_pcie_serdes_tbl),
+ .tx_tbl = msm8996_pcie_tx_tbl,
+ .tx_tbl_num = ARRAY_SIZE(msm8996_pcie_tx_tbl),
+ .rx_tbl = msm8996_pcie_rx_tbl,
+ .rx_tbl_num = ARRAY_SIZE(msm8996_pcie_rx_tbl),
+ .pcs_tbl = msm8996_pcie_pcs_tbl,
+ .pcs_tbl_num = ARRAY_SIZE(msm8996_pcie_pcs_tbl),
+ .clk_list = msm8996_phy_clk_l,
+ .num_clks = ARRAY_SIZE(msm8996_phy_clk_l),
+ .reset_list = msm8996_pciephy_reset_l,
+ .num_resets = ARRAY_SIZE(msm8996_pciephy_reset_l),
+ .vreg_list = msm8996_phy_vreg_l,
+ .num_vregs = ARRAY_SIZE(msm8996_phy_vreg_l),
+ .regs = pciephy_regs_layout,
+
+ .start_ctrl = PCS_START | PLL_READY_GATE_EN,
+ .pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL,
+ .mask_com_pcs_ready = PCS_READY,
+
+ .has_phy_com_ctrl = true,
+ .has_lane_rst = true,
+ .has_pwrdn_delay = true,
+ .pwrdn_delay_min = POWER_DOWN_DELAY_US_MIN,
+ .pwrdn_delay_max = POWER_DOWN_DELAY_US_MAX,
+};
+
+static const struct qmp_phy_cfg msm8996_usb3phy_cfg = {
+ .type = PHY_TYPE_USB3,
+ .nlanes = 1,
+
+ .serdes_tbl = msm8996_usb3_serdes_tbl,
+ .serdes_tbl_num = ARRAY_SIZE(msm8996_usb3_serdes_tbl),
+ .tx_tbl = msm8996_usb3_tx_tbl,
+ .tx_tbl_num = ARRAY_SIZE(msm8996_usb3_tx_tbl),
+ .rx_tbl = msm8996_usb3_rx_tbl,
+ .rx_tbl_num = ARRAY_SIZE(msm8996_usb3_rx_tbl),
+ .pcs_tbl = msm8996_usb3_pcs_tbl,
+ .pcs_tbl_num = ARRAY_SIZE(msm8996_usb3_pcs_tbl),
+ .clk_list = msm8996_phy_clk_l,
+ .num_clks = ARRAY_SIZE(msm8996_phy_clk_l),
+ .reset_list = msm8996_usb3phy_reset_l,
+ .num_resets = ARRAY_SIZE(msm8996_usb3phy_reset_l),
+ .vreg_list = msm8996_phy_vreg_l,
+ .num_vregs = ARRAY_SIZE(msm8996_phy_vreg_l),
+ .regs = usb3phy_regs_layout,
+
+ .start_ctrl = SERDES_START | PCS_START,
+ .pwrdn_ctrl = SW_PWRDN,
+ .mask_pcs_ready = PHYSTATUS,
+};
+
+static const char * const ipq8074_pciephy_clk_l[] = {
+ "aux", "cfg_ahb",
+};
+/* list of resets */
+static const char * const ipq8074_pciephy_reset_l[] = {
+ "phy", "common",
+};
+
+static const struct qmp_phy_cfg ipq8074_pciephy_cfg = {
+ .type = PHY_TYPE_PCIE,
+ .nlanes = 1,
+
+ .serdes_tbl = ipq8074_pcie_serdes_tbl,
+ .serdes_tbl_num = ARRAY_SIZE(ipq8074_pcie_serdes_tbl),
+ .tx_tbl = ipq8074_pcie_tx_tbl,
+ .tx_tbl_num = ARRAY_SIZE(ipq8074_pcie_tx_tbl),
+ .rx_tbl = ipq8074_pcie_rx_tbl,
+ .rx_tbl_num = ARRAY_SIZE(ipq8074_pcie_rx_tbl),
+ .pcs_tbl = ipq8074_pcie_pcs_tbl,
+ .pcs_tbl_num = ARRAY_SIZE(ipq8074_pcie_pcs_tbl),
+ .clk_list = ipq8074_pciephy_clk_l,
+ .num_clks = ARRAY_SIZE(ipq8074_pciephy_clk_l),
+ .reset_list = ipq8074_pciephy_reset_l,
+ .num_resets = ARRAY_SIZE(ipq8074_pciephy_reset_l),
+ .vreg_list = NULL,
+ .num_vregs = 0,
+ .regs = pciephy_regs_layout,
+
+ .start_ctrl = SERDES_START | PCS_START,
+ .pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL,
+ .mask_pcs_ready = PHYSTATUS,
+
+ .has_phy_com_ctrl = false,
+ .has_lane_rst = false,
+ .has_pwrdn_delay = true,
+ .pwrdn_delay_min = 995, /* us */
+ .pwrdn_delay_max = 1005, /* us */
+};
+
+static const struct qmp_phy_cfg qmp_v3_usb3phy_cfg = {
+ .type = PHY_TYPE_USB3,
+ .nlanes = 1,
+
+ .serdes_tbl = qmp_v3_usb3_serdes_tbl,
+ .serdes_tbl_num = ARRAY_SIZE(qmp_v3_usb3_serdes_tbl),
+ .tx_tbl = qmp_v3_usb3_tx_tbl,
+ .tx_tbl_num = ARRAY_SIZE(qmp_v3_usb3_tx_tbl),
+ .rx_tbl = qmp_v3_usb3_rx_tbl,
+ .rx_tbl_num = ARRAY_SIZE(qmp_v3_usb3_rx_tbl),
+ .pcs_tbl = qmp_v3_usb3_pcs_tbl,
+ .pcs_tbl_num = ARRAY_SIZE(qmp_v3_usb3_pcs_tbl),
+ .clk_list = qmp_v3_phy_clk_l,
+ .num_clks = ARRAY_SIZE(qmp_v3_phy_clk_l),
+ .reset_list = msm8996_usb3phy_reset_l,
+ .num_resets = ARRAY_SIZE(msm8996_usb3phy_reset_l),
+ .vreg_list = msm8996_phy_vreg_l,
+ .num_vregs = ARRAY_SIZE(msm8996_phy_vreg_l),
+ .regs = qmp_v3_usb3phy_regs_layout,
+
+ .start_ctrl = SERDES_START | PCS_START,
+ .pwrdn_ctrl = SW_PWRDN,
+ .mask_pcs_ready = PHYSTATUS,
+
+ .has_pwrdn_delay = true,
+ .pwrdn_delay_min = POWER_DOWN_DELAY_US_MIN,
+ .pwrdn_delay_max = POWER_DOWN_DELAY_US_MAX,
+
+ .has_phy_dp_com_ctrl = true,
+ .tx_b_lane_offset = 0x400,
+ .rx_b_lane_offset = 0x400,
+};
+
+static const struct qmp_phy_cfg qmp_v3_usb3_uniphy_cfg = {
+ .type = PHY_TYPE_USB3,
+ .nlanes = 1,
+
+ .serdes_tbl = qmp_v3_usb3_uniphy_serdes_tbl,
+ .serdes_tbl_num = ARRAY_SIZE(qmp_v3_usb3_uniphy_serdes_tbl),
+ .tx_tbl = qmp_v3_usb3_uniphy_tx_tbl,
+ .tx_tbl_num = ARRAY_SIZE(qmp_v3_usb3_uniphy_tx_tbl),
+ .rx_tbl = qmp_v3_usb3_uniphy_rx_tbl,
+ .rx_tbl_num = ARRAY_SIZE(qmp_v3_usb3_uniphy_rx_tbl),
+ .pcs_tbl = qmp_v3_usb3_uniphy_pcs_tbl,
+ .pcs_tbl_num = ARRAY_SIZE(qmp_v3_usb3_uniphy_pcs_tbl),
+ .clk_list = qmp_v3_phy_clk_l,
+ .num_clks = ARRAY_SIZE(qmp_v3_phy_clk_l),
+ .reset_list = msm8996_usb3phy_reset_l,
+ .num_resets = ARRAY_SIZE(msm8996_usb3phy_reset_l),
+ .vreg_list = msm8996_phy_vreg_l,
+ .num_vregs = ARRAY_SIZE(msm8996_phy_vreg_l),
+ .regs = qmp_v3_usb3phy_regs_layout,
+
+ .start_ctrl = SERDES_START | PCS_START,
+ .pwrdn_ctrl = SW_PWRDN,
+ .mask_pcs_ready = PHYSTATUS,
+
+ .has_pwrdn_delay = true,
+ .pwrdn_delay_min = POWER_DOWN_DELAY_US_MIN,
+ .pwrdn_delay_max = POWER_DOWN_DELAY_US_MAX,
+};
+
+static void qcom_qmp_phy_configure(void __iomem *base,
+ const unsigned int *regs,
+ const struct qmp_phy_init_tbl tbl[],
+ int num)
+{
+ int i;
+ const struct qmp_phy_init_tbl *t = tbl;
+
+ if (!t)
+ return;
+
+ for (i = 0; i < num; i++, t++) {
+ if (t->in_layout)
+ writel(t->val, base + regs[t->offset]);
+ else
+ writel(t->val, base + t->offset);
+ }
+}
+
+static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp)
+{
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ void __iomem *serdes = qmp->serdes;
+ void __iomem *dp_com = qmp->dp_com;
+ int ret, i;
+
+ mutex_lock(&qmp->phy_mutex);
+ if (qmp->init_count++) {
+ mutex_unlock(&qmp->phy_mutex);
+ return 0;
+ }
+
+ /* turn on regulator supplies */
+ ret = regulator_bulk_enable(cfg->num_vregs, qmp->vregs);
+ if (ret) {
+ dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret);
+ goto err_reg_enable;
+ }
+
+ for (i = 0; i < cfg->num_resets; i++) {
+ ret = reset_control_assert(qmp->resets[i]);
+ if (ret) {
+ dev_err(qmp->dev, "%s reset assert failed\n",
+ cfg->reset_list[i]);
+ goto err_rst_assert;
+ }
+ }
+
+ for (i = cfg->num_resets - 1; i >= 0; i--) {
+ ret = reset_control_deassert(qmp->resets[i]);
+ if (ret) {
+ dev_err(qmp->dev, "%s reset deassert failed\n",
+ qmp->cfg->reset_list[i]);
+ goto err_rst;
+ }
+ }
+
+ ret = clk_bulk_prepare_enable(cfg->num_clks, qmp->clks);
+ if (ret) {
+ dev_err(qmp->dev, "failed to enable clks, err=%d\n", ret);
+ goto err_rst;
+ }
+
+ if (cfg->has_phy_com_ctrl)
+ qphy_setbits(serdes, cfg->regs[QPHY_COM_POWER_DOWN_CONTROL],
+ SW_PWRDN);
+
+ if (cfg->has_phy_dp_com_ctrl) {
+ qphy_setbits(dp_com, QPHY_V3_DP_COM_POWER_DOWN_CTRL,
+ SW_PWRDN);
+ /* override hardware control for reset of qmp phy */
+ qphy_setbits(dp_com, QPHY_V3_DP_COM_RESET_OVRD_CTRL,
+ SW_DPPHY_RESET_MUX | SW_DPPHY_RESET |
+ SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET);
+
+ qphy_setbits(dp_com, QPHY_V3_DP_COM_PHY_MODE_CTRL,
+ USB3_MODE | DP_MODE);
+
+ /* bring both QMP USB and QMP DP PHYs PCS block out of reset */
+ qphy_clrbits(dp_com, QPHY_V3_DP_COM_RESET_OVRD_CTRL,
+ SW_DPPHY_RESET_MUX | SW_DPPHY_RESET |
+ SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET);
+ }
+
+ /* Serdes configuration */
+ qcom_qmp_phy_configure(serdes, cfg->regs, cfg->serdes_tbl,
+ cfg->serdes_tbl_num);
+
+ if (cfg->has_phy_com_ctrl) {
+ void __iomem *status;
+ unsigned int mask, val;
+
+ qphy_clrbits(serdes, cfg->regs[QPHY_COM_SW_RESET], SW_RESET);
+ qphy_setbits(serdes, cfg->regs[QPHY_COM_START_CONTROL],
+ SERDES_START | PCS_START);
+
+ status = serdes + cfg->regs[QPHY_COM_PCS_READY_STATUS];
+ mask = cfg->mask_com_pcs_ready;
+
+ ret = readl_poll_timeout(status, val, (val & mask), 10,
+ PHY_INIT_COMPLETE_TIMEOUT);
+ if (ret) {
+ dev_err(qmp->dev,
+ "phy common block init timed-out\n");
+ goto err_com_init;
+ }
+ }
+
+ mutex_unlock(&qmp->phy_mutex);
+
+ return 0;
+
+err_com_init:
+ clk_bulk_disable_unprepare(cfg->num_clks, qmp->clks);
+err_rst:
+ while (++i < cfg->num_resets)
+ reset_control_assert(qmp->resets[i]);
+err_rst_assert:
+ regulator_bulk_disable(cfg->num_vregs, qmp->vregs);
+err_reg_enable:
+ mutex_unlock(&qmp->phy_mutex);
+
+ return ret;
+}
+
+static int qcom_qmp_phy_com_exit(struct qcom_qmp *qmp)
+{
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ void __iomem *serdes = qmp->serdes;
+ int i = cfg->num_resets;
+
+ mutex_lock(&qmp->phy_mutex);
+ if (--qmp->init_count) {
+ mutex_unlock(&qmp->phy_mutex);
+ return 0;
+ }
+
+ if (cfg->has_phy_com_ctrl) {
+ qphy_setbits(serdes, cfg->regs[QPHY_COM_START_CONTROL],
+ SERDES_START | PCS_START);
+ qphy_clrbits(serdes, cfg->regs[QPHY_COM_SW_RESET],
+ SW_RESET);
+ qphy_setbits(serdes, cfg->regs[QPHY_COM_POWER_DOWN_CONTROL],
+ SW_PWRDN);
+ }
+
+ while (--i >= 0)
+ reset_control_assert(qmp->resets[i]);
+
+ clk_bulk_disable_unprepare(cfg->num_clks, qmp->clks);
+
+ regulator_bulk_disable(cfg->num_vregs, qmp->vregs);
+
+ mutex_unlock(&qmp->phy_mutex);
+
+ return 0;
+}
+
+/* PHY Initialization */
+static int qcom_qmp_phy_init(struct phy *phy)
+{
+ struct qmp_phy *qphy = phy_get_drvdata(phy);
+ struct qcom_qmp *qmp = qphy->qmp;
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ void __iomem *tx = qphy->tx;
+ void __iomem *rx = qphy->rx;
+ void __iomem *pcs = qphy->pcs;
+ void __iomem *dp_com = qmp->dp_com;
+ void __iomem *status;
+ unsigned int mask, val;
+ int ret;
+
+ dev_vdbg(qmp->dev, "Initializing QMP phy\n");
+
+ ret = qcom_qmp_phy_com_init(qmp);
+ if (ret)
+ return ret;
+
+ if (cfg->has_lane_rst) {
+ ret = reset_control_deassert(qphy->lane_rst);
+ if (ret) {
+ dev_err(qmp->dev, "lane%d reset deassert failed\n",
+ qphy->index);
+ goto err_lane_rst;
+ }
+ }
+
+ ret = clk_prepare_enable(qphy->pipe_clk);
+ if (ret) {
+ dev_err(qmp->dev, "pipe_clk enable failed err=%d\n", ret);
+ goto err_clk_enable;
+ }
+
+ /* Tx, Rx, and PCS configurations */
+ qcom_qmp_phy_configure(tx, cfg->regs, cfg->tx_tbl, cfg->tx_tbl_num);
+ /* Configuration for other LANE for USB-DP combo PHY */
+ if (cfg->has_phy_dp_com_ctrl)
+ qcom_qmp_phy_configure(tx + cfg->tx_b_lane_offset, cfg->regs,
+ cfg->tx_tbl, cfg->tx_tbl_num);
+
+ qcom_qmp_phy_configure(rx, cfg->regs, cfg->rx_tbl, cfg->rx_tbl_num);
+ if (cfg->has_phy_dp_com_ctrl)
+ qcom_qmp_phy_configure(rx + cfg->rx_b_lane_offset, cfg->regs,
+ cfg->rx_tbl, cfg->rx_tbl_num);
+
+ qcom_qmp_phy_configure(pcs, cfg->regs, cfg->pcs_tbl, cfg->pcs_tbl_num);
+
+ /*
+ * Pull out PHY from POWER DOWN state.
+ * This is active low enable signal to power-down PHY.
+ */
+ qphy_setbits(pcs, QPHY_POWER_DOWN_CONTROL, cfg->pwrdn_ctrl);
+
+ if (cfg->has_pwrdn_delay)
+ usleep_range(cfg->pwrdn_delay_min, cfg->pwrdn_delay_max);
+
+ /* Pull PHY out of reset state */
+ qphy_clrbits(pcs, cfg->regs[QPHY_SW_RESET], SW_RESET);
+ if (cfg->has_phy_dp_com_ctrl)
+ qphy_clrbits(dp_com, QPHY_V3_DP_COM_SW_RESET, SW_RESET);
+
+ /* start SerDes and Phy-Coding-Sublayer */
+ qphy_setbits(pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl);
+
+ status = pcs + cfg->regs[QPHY_PCS_READY_STATUS];
+ mask = cfg->mask_pcs_ready;
+
+ ret = readl_poll_timeout(status, val, !(val & mask), 1,
+ PHY_INIT_COMPLETE_TIMEOUT);
+ if (ret) {
+ dev_err(qmp->dev, "phy initialization timed-out\n");
+ goto err_pcs_ready;
+ }
+ qmp->phy_initialized = true;
+
+ return ret;
+
+err_pcs_ready:
+ clk_disable_unprepare(qphy->pipe_clk);
+err_clk_enable:
+ if (cfg->has_lane_rst)
+ reset_control_assert(qphy->lane_rst);
+err_lane_rst:
+ qcom_qmp_phy_com_exit(qmp);
+
+ return ret;
+}
+
+static int qcom_qmp_phy_exit(struct phy *phy)
+{
+ struct qmp_phy *qphy = phy_get_drvdata(phy);
+ struct qcom_qmp *qmp = qphy->qmp;
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+
+ clk_disable_unprepare(qphy->pipe_clk);
+
+ /* PHY reset */
+ qphy_setbits(qphy->pcs, cfg->regs[QPHY_SW_RESET], SW_RESET);
+
+ /* stop SerDes and Phy-Coding-Sublayer */
+ qphy_clrbits(qphy->pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl);
+
+ /* Put PHY into POWER DOWN state: active low */
+ qphy_clrbits(qphy->pcs, QPHY_POWER_DOWN_CONTROL, cfg->pwrdn_ctrl);
+
+ if (cfg->has_lane_rst)
+ reset_control_assert(qphy->lane_rst);
+
+ qcom_qmp_phy_com_exit(qmp);
+
+ qmp->phy_initialized = false;
+
+ return 0;
+}
+
+static int qcom_qmp_phy_set_mode(struct phy *phy, enum phy_mode mode)
+{
+ struct qmp_phy *qphy = phy_get_drvdata(phy);
+ struct qcom_qmp *qmp = qphy->qmp;
+
+ qmp->mode = mode;
+
+ return 0;
+}
+
+static void qcom_qmp_phy_enable_autonomous_mode(struct qmp_phy *qphy)
+{
+ struct qcom_qmp *qmp = qphy->qmp;
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ void __iomem *pcs = qphy->pcs;
+ void __iomem *pcs_misc = qphy->pcs_misc;
+ u32 intr_mask;
+
+ if (qmp->mode == PHY_MODE_USB_HOST_SS ||
+ qmp->mode == PHY_MODE_USB_DEVICE_SS)
+ intr_mask = ARCVR_DTCT_EN | ALFPS_DTCT_EN;
+ else
+ intr_mask = ARCVR_DTCT_EN | ARCVR_DTCT_EVENT_SEL;
+
+ /* Clear any pending interrupts status */
+ qphy_setbits(pcs, cfg->regs[QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR], IRQ_CLEAR);
+ /* Writing 1 followed by 0 clears the interrupt */
+ qphy_clrbits(pcs, cfg->regs[QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR], IRQ_CLEAR);
+
+ qphy_clrbits(pcs, cfg->regs[QPHY_PCS_AUTONOMOUS_MODE_CTRL],
+ ARCVR_DTCT_EN | ALFPS_DTCT_EN | ARCVR_DTCT_EVENT_SEL);
+
+ /* Enable required PHY autonomous mode interrupts */
+ qphy_setbits(pcs, cfg->regs[QPHY_PCS_AUTONOMOUS_MODE_CTRL], intr_mask);
+
+ /* Enable i/o clamp_n for autonomous mode */
+ if (pcs_misc)
+ qphy_clrbits(pcs_misc, QPHY_V3_PCS_MISC_CLAMP_ENABLE, CLAMP_EN);
+}
+
+static void qcom_qmp_phy_disable_autonomous_mode(struct qmp_phy *qphy)
+{
+ struct qcom_qmp *qmp = qphy->qmp;
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ void __iomem *pcs = qphy->pcs;
+ void __iomem *pcs_misc = qphy->pcs_misc;
+
+ /* Disable i/o clamp_n on resume for normal mode */
+ if (pcs_misc)
+ qphy_setbits(pcs_misc, QPHY_V3_PCS_MISC_CLAMP_ENABLE, CLAMP_EN);
+
+ qphy_clrbits(pcs, cfg->regs[QPHY_PCS_AUTONOMOUS_MODE_CTRL],
+ ARCVR_DTCT_EN | ARCVR_DTCT_EVENT_SEL | ALFPS_DTCT_EN);
+
+ qphy_setbits(pcs, cfg->regs[QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR], IRQ_CLEAR);
+ /* Writing 1 followed by 0 clears the interrupt */
+ qphy_clrbits(pcs, cfg->regs[QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR], IRQ_CLEAR);
+}
+
+static int __maybe_unused qcom_qmp_phy_runtime_suspend(struct device *dev)
+{
+ struct qcom_qmp *qmp = dev_get_drvdata(dev);
+ struct qmp_phy *qphy = qmp->phys[0];
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+
+ dev_vdbg(dev, "Suspending QMP phy, mode:%d\n", qmp->mode);
+
+ /* Supported only for USB3 PHY */
+ if (cfg->type != PHY_TYPE_USB3)
+ return 0;
+
+ if (!qmp->phy_initialized) {
+ dev_vdbg(dev, "PHY not initialized, bailing out\n");
+ return 0;
+ }
+
+ qcom_qmp_phy_enable_autonomous_mode(qphy);
+
+ clk_disable_unprepare(qphy->pipe_clk);
+ clk_bulk_disable_unprepare(cfg->num_clks, qmp->clks);
+
+ return 0;
+}
+
+static int __maybe_unused qcom_qmp_phy_runtime_resume(struct device *dev)
+{
+ struct qcom_qmp *qmp = dev_get_drvdata(dev);
+ struct qmp_phy *qphy = qmp->phys[0];
+ const struct qmp_phy_cfg *cfg = qmp->cfg;
+ int ret = 0;
+
+ dev_vdbg(dev, "Resuming QMP phy, mode:%d\n", qmp->mode);
+
+ /* Supported only for USB3 PHY */
+ if (cfg->type != PHY_TYPE_USB3)
+ return 0;
+
+ if (!qmp->phy_initialized) {
+ dev_vdbg(dev, "PHY not initialized, bailing out\n");
+ return 0;
+ }
+
+ ret = clk_bulk_prepare_enable(cfg->num_clks, qmp->clks);
+ if (ret) {
+ dev_err(qmp->dev, "failed to enable clks, err=%d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(qphy->pipe_clk);
+ if (ret) {
+ dev_err(dev, "pipe_clk enable failed, err=%d\n", ret);
+ clk_bulk_disable_unprepare(cfg->num_clks, qmp->clks);
+ return ret;
+ }
+
+ qcom_qmp_phy_disable_autonomous_mode(qphy);
+
+ return 0;
+}
+
+static int qcom_qmp_phy_vreg_init(struct device *dev)
+{
+ struct qcom_qmp *qmp = dev_get_drvdata(dev);
+ int num = qmp->cfg->num_vregs;
+ int i;
+
+ qmp->vregs = devm_kcalloc(dev, num, sizeof(*qmp->vregs), GFP_KERNEL);
+ if (!qmp->vregs)
+ return -ENOMEM;
+
+ for (i = 0; i < num; i++)
+ qmp->vregs[i].supply = qmp->cfg->vreg_list[i];
+
+ return devm_regulator_bulk_get(dev, num, qmp->vregs);
+}
+
+static int qcom_qmp_phy_reset_init(struct device *dev)
+{
+ struct qcom_qmp *qmp = dev_get_drvdata(dev);
+ int i;
+
+ qmp->resets = devm_kcalloc(dev, qmp->cfg->num_resets,
+ sizeof(*qmp->resets), GFP_KERNEL);
+ if (!qmp->resets)
+ return -ENOMEM;
+
+ for (i = 0; i < qmp->cfg->num_resets; i++) {
+ struct reset_control *rst;
+ const char *name = qmp->cfg->reset_list[i];
+
+ rst = devm_reset_control_get(dev, name);
+ if (IS_ERR(rst)) {
+ dev_err(dev, "failed to get %s reset\n", name);
+ return PTR_ERR(rst);
+ }
+ qmp->resets[i] = rst;
+ }
+
+ return 0;
+}
+
+static int qcom_qmp_phy_clk_init(struct device *dev)
+{
+ struct qcom_qmp *qmp = dev_get_drvdata(dev);
+ int num = qmp->cfg->num_clks;
+ int i;
+
+ qmp->clks = devm_kcalloc(dev, num, sizeof(*qmp->clks), GFP_KERNEL);
+ if (!qmp->clks)
+ return -ENOMEM;
+
+ for (i = 0; i < num; i++)
+ qmp->clks[i].id = qmp->cfg->clk_list[i];
+
+ return devm_clk_bulk_get(dev, num, qmp->clks);
+}
+
+/*
+ * Register a fixed rate pipe clock.
+ *
+ * The <s>_pipe_clksrc generated by PHY goes to the GCC that gate
+ * controls it. The <s>_pipe_clk coming out of the GCC is requested
+ * by the PHY driver for its operations.
+ * We register the <s>_pipe_clksrc here. The gcc driver takes care
+ * of assigning this <s>_pipe_clksrc as parent to <s>_pipe_clk.
+ * Below picture shows this relationship.
+ *
+ * +---------------+
+ * | PHY block |<<---------------------------------------+
+ * | | |
+ * | +-------+ | +-----+ |
+ * I/P---^-->| PLL |---^--->pipe_clksrc--->| GCC |--->pipe_clk---+
+ * clk | +-------+ | +-----+
+ * +---------------+
+ */
+static int phy_pipe_clk_register(struct qcom_qmp *qmp, struct device_node *np)
+{
+ struct clk_fixed_rate *fixed;
+ struct clk_init_data init = { };
+ int ret;
+
+ if ((qmp->cfg->type != PHY_TYPE_USB3) &&
+ (qmp->cfg->type != PHY_TYPE_PCIE)) {
+ /* not all phys register pipe clocks, so return success */
+ return 0;
+ }
+
+ ret = of_property_read_string(np, "clock-output-names", &init.name);
+ if (ret) {
+ dev_err(qmp->dev, "%s: No clock-output-names\n", np->name);
+ return ret;
+ }
+
+ fixed = devm_kzalloc(qmp->dev, sizeof(*fixed), GFP_KERNEL);
+ if (!fixed)
+ return -ENOMEM;
+
+ init.ops = &clk_fixed_rate_ops;
+
+ /* controllers using QMP phys use 125MHz pipe clock interface */
+ fixed->fixed_rate = 125000000;
+ fixed->hw.init = &init;
+
+ return devm_clk_hw_register(qmp->dev, &fixed->hw);
+}
+
+static const struct phy_ops qcom_qmp_phy_gen_ops = {
+ .init = qcom_qmp_phy_init,
+ .exit = qcom_qmp_phy_exit,
+ .set_mode = qcom_qmp_phy_set_mode,
+ .owner = THIS_MODULE,
+};
+
+static void qcom_qmp_reset_control_put(void *data)
+{
+ reset_control_put(data);
+}
+
+static
+int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id)
+{
+ struct qcom_qmp *qmp = dev_get_drvdata(dev);
+ struct phy *generic_phy;
+ struct qmp_phy *qphy;
+ char prop_name[MAX_PROP_NAME];
+ int ret;
+
+ qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL);
+ if (!qphy)
+ return -ENOMEM;
+
+ /*
+ * Get memory resources for each phy lane:
+ * Resources are indexed as: tx -> 0; rx -> 1; pcs -> 2; and
+ * pcs_misc (optional) -> 3.
+ */
+ qphy->tx = of_iomap(np, 0);
+ if (!qphy->tx)
+ return -ENOMEM;
+
+ qphy->rx = of_iomap(np, 1);
+ if (!qphy->rx)
+ return -ENOMEM;
+
+ qphy->pcs = of_iomap(np, 2);
+ if (!qphy->pcs)
+ return -ENOMEM;
+
+ qphy->pcs_misc = of_iomap(np, 3);
+ if (!qphy->pcs_misc)
+ dev_vdbg(dev, "PHY pcs_misc-reg not used\n");
+
+ /*
+ * Get PHY's Pipe clock, if any. USB3 and PCIe are PIPE3
+ * based phys, so they essentially have pipe clock. So,
+ * we return error in case phy is USB3 or PIPE type.
+ * Otherwise, we initialize pipe clock to NULL for
+ * all phys that don't need this.
+ */
+ snprintf(prop_name, sizeof(prop_name), "pipe%d", id);
+ qphy->pipe_clk = devm_get_clk_from_child(dev, np, prop_name);
+ if (IS_ERR(qphy->pipe_clk)) {
+ if (qmp->cfg->type == PHY_TYPE_PCIE ||
+ qmp->cfg->type == PHY_TYPE_USB3) {
+ ret = PTR_ERR(qphy->pipe_clk);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev,
+ "failed to get lane%d pipe_clk, %d\n",
+ id, ret);
+ return ret;
+ }
+ qphy->pipe_clk = NULL;
+ }
+
+ /* Get lane reset, if any */
+ if (qmp->cfg->has_lane_rst) {
+ snprintf(prop_name, sizeof(prop_name), "lane%d", id);
+ qphy->lane_rst = of_reset_control_get(np, prop_name);
+ if (IS_ERR(qphy->lane_rst)) {
+ dev_err(dev, "failed to get lane%d reset\n", id);
+ return PTR_ERR(qphy->lane_rst);
+ }
+ ret = devm_add_action_or_reset(dev, qcom_qmp_reset_control_put,
+ qphy->lane_rst);
+ if (ret)
+ return ret;
+ }
+
+ generic_phy = devm_phy_create(dev, np, &qcom_qmp_phy_gen_ops);
+ if (IS_ERR(generic_phy)) {
+ ret = PTR_ERR(generic_phy);
+ dev_err(dev, "failed to create qphy %d\n", ret);
+ return ret;
+ }
+
+ qphy->phy = generic_phy;
+ qphy->index = id;
+ qphy->qmp = qmp;
+ qmp->phys[id] = qphy;
+ phy_set_drvdata(generic_phy, qphy);
+
+ return 0;
+}
+
+static const struct of_device_id qcom_qmp_phy_of_match_table[] = {
+ {
+ .compatible = "qcom,msm8996-qmp-pcie-phy",
+ .data = &msm8996_pciephy_cfg,
+ }, {
+ .compatible = "qcom,msm8996-qmp-usb3-phy",
+ .data = &msm8996_usb3phy_cfg,
+ }, {
+ .compatible = "qcom,ipq8074-qmp-pcie-phy",
+ .data = &ipq8074_pciephy_cfg,
+ }, {
+ .compatible = "qcom,sdm845-qmp-usb3-phy",
+ .data = &qmp_v3_usb3phy_cfg,
+ }, {
+ .compatible = "qcom,sdm845-qmp-usb3-uni-phy",
+ .data = &qmp_v3_usb3_uniphy_cfg,
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, qcom_qmp_phy_of_match_table);
+
+static const struct dev_pm_ops qcom_qmp_phy_pm_ops = {
+ SET_RUNTIME_PM_OPS(qcom_qmp_phy_runtime_suspend,
+ qcom_qmp_phy_runtime_resume, NULL)
+};
+
+static int qcom_qmp_phy_probe(struct platform_device *pdev)
+{
+ struct qcom_qmp *qmp;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct device_node *child;
+ struct phy_provider *phy_provider;
+ void __iomem *base;
+ int num, id;
+ int ret;
+
+ qmp = devm_kzalloc(dev, sizeof(*qmp), GFP_KERNEL);
+ if (!qmp)
+ return -ENOMEM;
+
+ qmp->dev = dev;
+ dev_set_drvdata(dev, qmp);
+
+ /* Get the specific init parameters of QMP phy */
+ qmp->cfg = of_device_get_match_data(dev);
+ if (!qmp->cfg)
+ return -EINVAL;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ /* per PHY serdes; usually located at base address */
+ qmp->serdes = base;
+
+ /* per PHY dp_com; if PHY has dp_com control block */
+ if (qmp->cfg->has_phy_dp_com_ctrl) {
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "dp_com");
+ base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ qmp->dp_com = base;
+ }
+
+ mutex_init(&qmp->phy_mutex);
+
+ ret = qcom_qmp_phy_clk_init(dev);
+ if (ret)
+ return ret;
+
+ ret = qcom_qmp_phy_reset_init(dev);
+ if (ret)
+ return ret;
+
+ ret = qcom_qmp_phy_vreg_init(dev);
+ if (ret) {
+ dev_err(dev, "failed to get regulator supplies\n");
+ return ret;
+ }
+
+ num = of_get_available_child_count(dev->of_node);
+ /* do we have a rogue child node ? */
+ if (num > qmp->cfg->nlanes)
+ return -EINVAL;
+
+ qmp->phys = devm_kcalloc(dev, num, sizeof(*qmp->phys), GFP_KERNEL);
+ if (!qmp->phys)
+ return -ENOMEM;
+
+ id = 0;
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ /*
+ * Prevent runtime pm from being ON by default. Users can enable
+ * it using power/control in sysfs.
+ */
+ pm_runtime_forbid(dev);
+
+ for_each_available_child_of_node(dev->of_node, child) {
+ /* Create per-lane phy */
+ ret = qcom_qmp_phy_create(dev, child, id);
+ if (ret) {
+ dev_err(dev, "failed to create lane%d phy, %d\n",
+ id, ret);
+ pm_runtime_disable(dev);
+ return ret;
+ }
+
+ /*
+ * Register the pipe clock provided by phy.
+ * See function description to see details of this pipe clock.
+ */
+ ret = phy_pipe_clk_register(qmp, child);
+ if (ret) {
+ dev_err(qmp->dev,
+ "failed to register pipe clock source\n");
+ pm_runtime_disable(dev);
+ return ret;
+ }
+ id++;
+ }
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (!IS_ERR(phy_provider))
+ dev_info(dev, "Registered Qcom-QMP phy\n");
+ else
+ pm_runtime_disable(dev);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static struct platform_driver qcom_qmp_phy_driver = {
+ .probe = qcom_qmp_phy_probe,
+ .driver = {
+ .name = "qcom-qmp-phy",
+ .pm = &qcom_qmp_phy_pm_ops,
+ .of_match_table = qcom_qmp_phy_of_match_table,
+ },
+};
+
+module_platform_driver(qcom_qmp_phy_driver);
+
+MODULE_AUTHOR("Vivek Gautam <vivek.gautam@codeaurora.org>");
+MODULE_DESCRIPTION("Qualcomm QMP PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.h b/drivers/phy/qualcomm/phy-qcom-qmp.h
new file mode 100644
index 000000000..6b3aaf521
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-qmp.h
@@ -0,0 +1,287 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef QCOM_PHY_QMP_H_
+#define QCOM_PHY_QMP_H_
+
+/* Only for QMP V2 PHY - QSERDES COM registers */
+#define QSERDES_COM_BG_TIMER 0x00c
+#define QSERDES_COM_SSC_EN_CENTER 0x010
+#define QSERDES_COM_SSC_ADJ_PER1 0x014
+#define QSERDES_COM_SSC_ADJ_PER2 0x018
+#define QSERDES_COM_SSC_PER1 0x01c
+#define QSERDES_COM_SSC_PER2 0x020
+#define QSERDES_COM_SSC_STEP_SIZE1 0x024
+#define QSERDES_COM_SSC_STEP_SIZE2 0x028
+#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN 0x034
+#define QSERDES_COM_CLK_ENABLE1 0x038
+#define QSERDES_COM_SYS_CLK_CTRL 0x03c
+#define QSERDES_COM_SYSCLK_BUF_ENABLE 0x040
+#define QSERDES_COM_PLL_IVCO 0x048
+#define QSERDES_COM_LOCK_CMP1_MODE0 0x04c
+#define QSERDES_COM_LOCK_CMP2_MODE0 0x050
+#define QSERDES_COM_LOCK_CMP3_MODE0 0x054
+#define QSERDES_COM_LOCK_CMP1_MODE1 0x058
+#define QSERDES_COM_LOCK_CMP2_MODE1 0x05c
+#define QSERDES_COM_LOCK_CMP3_MODE1 0x060
+#define QSERDES_COM_BG_TRIM 0x070
+#define QSERDES_COM_CLK_EP_DIV 0x074
+#define QSERDES_COM_CP_CTRL_MODE0 0x078
+#define QSERDES_COM_CP_CTRL_MODE1 0x07c
+#define QSERDES_COM_PLL_RCTRL_MODE0 0x084
+#define QSERDES_COM_PLL_RCTRL_MODE1 0x088
+#define QSERDES_COM_PLL_CCTRL_MODE0 0x090
+#define QSERDES_COM_PLL_CCTRL_MODE1 0x094
+#define QSERDES_COM_BIAS_EN_CTRL_BY_PSM 0x0a8
+#define QSERDES_COM_SYSCLK_EN_SEL 0x0ac
+#define QSERDES_COM_RESETSM_CNTRL 0x0b4
+#define QSERDES_COM_RESTRIM_CTRL 0x0bc
+#define QSERDES_COM_RESCODE_DIV_NUM 0x0c4
+#define QSERDES_COM_LOCK_CMP_EN 0x0c8
+#define QSERDES_COM_LOCK_CMP_CFG 0x0cc
+#define QSERDES_COM_DEC_START_MODE0 0x0d0
+#define QSERDES_COM_DEC_START_MODE1 0x0d4
+#define QSERDES_COM_DIV_FRAC_START1_MODE0 0x0dc
+#define QSERDES_COM_DIV_FRAC_START2_MODE0 0x0e0
+#define QSERDES_COM_DIV_FRAC_START3_MODE0 0x0e4
+#define QSERDES_COM_DIV_FRAC_START1_MODE1 0x0e8
+#define QSERDES_COM_DIV_FRAC_START2_MODE1 0x0ec
+#define QSERDES_COM_DIV_FRAC_START3_MODE1 0x0f0
+#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 0x108
+#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 0x10c
+#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1 0x110
+#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1 0x114
+#define QSERDES_COM_VCO_TUNE_CTRL 0x124
+#define QSERDES_COM_VCO_TUNE_MAP 0x128
+#define QSERDES_COM_VCO_TUNE1_MODE0 0x12c
+#define QSERDES_COM_VCO_TUNE2_MODE0 0x130
+#define QSERDES_COM_VCO_TUNE1_MODE1 0x134
+#define QSERDES_COM_VCO_TUNE2_MODE1 0x138
+#define QSERDES_COM_VCO_TUNE_TIMER1 0x144
+#define QSERDES_COM_VCO_TUNE_TIMER2 0x148
+#define QSERDES_COM_BG_CTRL 0x170
+#define QSERDES_COM_CLK_SELECT 0x174
+#define QSERDES_COM_HSCLK_SEL 0x178
+#define QSERDES_COM_CORECLK_DIV 0x184
+#define QSERDES_COM_CORE_CLK_EN 0x18c
+#define QSERDES_COM_C_READY_STATUS 0x190
+#define QSERDES_COM_CMN_CONFIG 0x194
+#define QSERDES_COM_SVS_MODE_CLK_SEL 0x19c
+#define QSERDES_COM_DEBUG_BUS0 0x1a0
+#define QSERDES_COM_DEBUG_BUS1 0x1a4
+#define QSERDES_COM_DEBUG_BUS2 0x1a8
+#define QSERDES_COM_DEBUG_BUS3 0x1ac
+#define QSERDES_COM_DEBUG_BUS_SEL 0x1b0
+#define QSERDES_COM_CORECLK_DIV_MODE1 0x1bc
+
+/* Only for QMP V2 PHY - TX registers */
+#define QSERDES_TX_EMP_POST1_LVL 0x018
+#define QSERDES_TX_SLEW_CNTL 0x040
+#define QSERDES_TX_RES_CODE_LANE_OFFSET 0x054
+#define QSERDES_TX_DEBUG_BUS_SEL 0x064
+#define QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN 0x068
+#define QSERDES_TX_LANE_MODE 0x094
+#define QSERDES_TX_RCV_DETECT_LVL_2 0x0ac
+
+/* Only for QMP V2 PHY - RX registers */
+#define QSERDES_RX_UCDR_SO_GAIN_HALF 0x010
+#define QSERDES_RX_UCDR_SO_GAIN 0x01c
+#define QSERDES_RX_UCDR_FASTLOCK_FO_GAIN 0x040
+#define QSERDES_RX_UCDR_SO_SATURATION_AND_ENABLE 0x048
+#define QSERDES_RX_RX_TERM_BW 0x090
+#define QSERDES_RX_RX_EQ_GAIN1_LSB 0x0c4
+#define QSERDES_RX_RX_EQ_GAIN1_MSB 0x0c8
+#define QSERDES_RX_RX_EQ_GAIN2_LSB 0x0cc
+#define QSERDES_RX_RX_EQ_GAIN2_MSB 0x0d0
+#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2 0x0d8
+#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3 0x0dc
+#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4 0x0e0
+#define QSERDES_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1 0x108
+#define QSERDES_RX_RX_OFFSET_ADAPTOR_CNTRL2 0x10c
+#define QSERDES_RX_SIGDET_ENABLES 0x110
+#define QSERDES_RX_SIGDET_CNTRL 0x114
+#define QSERDES_RX_SIGDET_LVL 0x118
+#define QSERDES_RX_SIGDET_DEGLITCH_CNTRL 0x11c
+#define QSERDES_RX_RX_BAND 0x120
+#define QSERDES_RX_RX_INTERFACE_MODE 0x12c
+
+/* Only for QMP V2 PHY - PCS registers */
+#define QPHY_POWER_DOWN_CONTROL 0x04
+#define QPHY_TXDEEMPH_M6DB_V0 0x24
+#define QPHY_TXDEEMPH_M3P5DB_V0 0x28
+#define QPHY_ENDPOINT_REFCLK_DRIVE 0x54
+#define QPHY_RX_IDLE_DTCT_CNTRL 0x58
+#define QPHY_POWER_STATE_CONFIG1 0x60
+#define QPHY_POWER_STATE_CONFIG2 0x64
+#define QPHY_POWER_STATE_CONFIG4 0x6c
+#define QPHY_LOCK_DETECT_CONFIG1 0x80
+#define QPHY_LOCK_DETECT_CONFIG2 0x84
+#define QPHY_LOCK_DETECT_CONFIG3 0x88
+#define QPHY_PWRUP_RESET_DLY_TIME_AUXCLK 0xa0
+#define QPHY_LP_WAKEUP_DLY_TIME_AUXCLK 0xa4
+#define QPHY_PLL_LOCK_CHK_DLY_TIME_AUXCLK_LSB 0x1A8
+#define QPHY_OSC_DTCT_ACTIONS 0x1AC
+#define QPHY_RX_SIGDET_LVL 0x1D8
+#define QPHY_L1SS_WAKEUP_DLY_TIME_AUXCLK_LSB 0x1DC
+#define QPHY_L1SS_WAKEUP_DLY_TIME_AUXCLK_MSB 0x1E0
+
+/* Only for QMP V3 PHY - DP COM registers */
+#define QPHY_V3_DP_COM_PHY_MODE_CTRL 0x00
+#define QPHY_V3_DP_COM_SW_RESET 0x04
+#define QPHY_V3_DP_COM_POWER_DOWN_CTRL 0x08
+#define QPHY_V3_DP_COM_SWI_CTRL 0x0c
+#define QPHY_V3_DP_COM_TYPEC_CTRL 0x10
+#define QPHY_V3_DP_COM_TYPEC_PWRDN_CTRL 0x14
+#define QPHY_V3_DP_COM_RESET_OVRD_CTRL 0x1c
+
+/* Only for QMP V3 PHY - QSERDES COM registers */
+#define QSERDES_V3_COM_BG_TIMER 0x00c
+#define QSERDES_V3_COM_SSC_EN_CENTER 0x010
+#define QSERDES_V3_COM_SSC_ADJ_PER1 0x014
+#define QSERDES_V3_COM_SSC_ADJ_PER2 0x018
+#define QSERDES_V3_COM_SSC_PER1 0x01c
+#define QSERDES_V3_COM_SSC_PER2 0x020
+#define QSERDES_V3_COM_SSC_STEP_SIZE1 0x024
+#define QSERDES_V3_COM_SSC_STEP_SIZE2 0x028
+#define QSERDES_V3_COM_BIAS_EN_CLKBUFLR_EN 0x034
+#define QSERDES_V3_COM_CLK_ENABLE1 0x038
+#define QSERDES_V3_COM_SYS_CLK_CTRL 0x03c
+#define QSERDES_V3_COM_SYSCLK_BUF_ENABLE 0x040
+#define QSERDES_V3_COM_PLL_IVCO 0x048
+#define QSERDES_V3_COM_LOCK_CMP1_MODE0 0x098
+#define QSERDES_V3_COM_LOCK_CMP2_MODE0 0x09c
+#define QSERDES_V3_COM_LOCK_CMP3_MODE0 0x0a0
+#define QSERDES_V3_COM_LOCK_CMP1_MODE1 0x0a4
+#define QSERDES_V3_COM_LOCK_CMP2_MODE1 0x0a8
+#define QSERDES_V3_COM_LOCK_CMP3_MODE1 0x0ac
+#define QSERDES_V3_COM_CLK_EP_DIV 0x05c
+#define QSERDES_V3_COM_CP_CTRL_MODE0 0x060
+#define QSERDES_V3_COM_CP_CTRL_MODE1 0x064
+#define QSERDES_V3_COM_PLL_RCTRL_MODE0 0x068
+#define QSERDES_V3_COM_PLL_RCTRL_MODE1 0x06c
+#define QSERDES_V3_COM_PLL_CCTRL_MODE0 0x070
+#define QSERDES_V3_COM_PLL_CCTRL_MODE1 0x074
+#define QSERDES_V3_COM_SYSCLK_EN_SEL 0x080
+#define QSERDES_V3_COM_RESETSM_CNTRL 0x088
+#define QSERDES_V3_COM_RESETSM_CNTRL2 0x08c
+#define QSERDES_V3_COM_LOCK_CMP_EN 0x090
+#define QSERDES_V3_COM_LOCK_CMP_CFG 0x094
+#define QSERDES_V3_COM_DEC_START_MODE0 0x0b0
+#define QSERDES_V3_COM_DEC_START_MODE1 0x0b4
+#define QSERDES_V3_COM_DIV_FRAC_START1_MODE0 0x0b8
+#define QSERDES_V3_COM_DIV_FRAC_START2_MODE0 0x0bc
+#define QSERDES_V3_COM_DIV_FRAC_START3_MODE0 0x0c0
+#define QSERDES_V3_COM_DIV_FRAC_START1_MODE1 0x0c4
+#define QSERDES_V3_COM_DIV_FRAC_START2_MODE1 0x0c8
+#define QSERDES_V3_COM_DIV_FRAC_START3_MODE1 0x0cc
+#define QSERDES_V3_COM_INTEGLOOP_GAIN0_MODE0 0x0d8
+#define QSERDES_V3_COM_INTEGLOOP_GAIN1_MODE0 0x0dc
+#define QSERDES_V3_COM_INTEGLOOP_GAIN0_MODE1 0x0e0
+#define QSERDES_V3_COM_INTEGLOOP_GAIN1_MODE1 0x0e4
+#define QSERDES_V3_COM_VCO_TUNE_CTRL 0x0ec
+#define QSERDES_V3_COM_VCO_TUNE_MAP 0x0f0
+#define QSERDES_V3_COM_VCO_TUNE1_MODE0 0x0f4
+#define QSERDES_V3_COM_VCO_TUNE2_MODE0 0x0f8
+#define QSERDES_V3_COM_VCO_TUNE1_MODE1 0x0fc
+#define QSERDES_V3_COM_VCO_TUNE2_MODE1 0x100
+#define QSERDES_V3_COM_VCO_TUNE_TIMER1 0x11c
+#define QSERDES_V3_COM_VCO_TUNE_TIMER2 0x120
+#define QSERDES_V3_COM_CLK_SELECT 0x138
+#define QSERDES_V3_COM_HSCLK_SEL 0x13c
+#define QSERDES_V3_COM_CORECLK_DIV_MODE0 0x148
+#define QSERDES_V3_COM_CORECLK_DIV_MODE1 0x14c
+#define QSERDES_V3_COM_CORE_CLK_EN 0x154
+#define QSERDES_V3_COM_C_READY_STATUS 0x158
+#define QSERDES_V3_COM_CMN_CONFIG 0x15c
+#define QSERDES_V3_COM_SVS_MODE_CLK_SEL 0x164
+#define QSERDES_V3_COM_DEBUG_BUS0 0x168
+#define QSERDES_V3_COM_DEBUG_BUS1 0x16c
+#define QSERDES_V3_COM_DEBUG_BUS2 0x170
+#define QSERDES_V3_COM_DEBUG_BUS3 0x174
+#define QSERDES_V3_COM_DEBUG_BUS_SEL 0x178
+
+/* Only for QMP V3 PHY - TX registers */
+#define QSERDES_V3_TX_RES_CODE_LANE_OFFSET_TX 0x044
+#define QSERDES_V3_TX_RES_CODE_LANE_OFFSET_RX 0x048
+#define QSERDES_V3_TX_DEBUG_BUS_SEL 0x058
+#define QSERDES_V3_TX_HIGHZ_DRVR_EN 0x060
+#define QSERDES_V3_TX_LANE_MODE_1 0x08c
+#define QSERDES_V3_TX_RCV_DETECT_LVL_2 0x0a4
+
+/* Only for QMP V3 PHY - RX registers */
+#define QSERDES_V3_RX_UCDR_SO_GAIN_HALF 0x00c
+#define QSERDES_V3_RX_UCDR_SO_GAIN 0x014
+#define QSERDES_V3_RX_UCDR_FASTLOCK_FO_GAIN 0x030
+#define QSERDES_V3_RX_UCDR_SO_SATURATION_AND_ENABLE 0x034
+#define QSERDES_V3_RX_RX_TERM_BW 0x07c
+#define QSERDES_V3_RX_VGA_CAL_CNTRL1 0x0bc
+#define QSERDES_V3_RX_VGA_CAL_CNTRL2 0x0c0
+#define QSERDES_V3_RX_RX_EQ_GAIN2_LSB 0x0c8
+#define QSERDES_V3_RX_RX_EQ_GAIN2_MSB 0x0cc
+#define QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL2 0x0d4
+#define QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL3 0x0d8
+#define QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL4 0x0dc
+#define QSERDES_V3_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1 0x0f8
+#define QSERDES_V3_RX_RX_OFFSET_ADAPTOR_CNTRL2 0x0fc
+#define QSERDES_V3_RX_SIGDET_ENABLES 0x100
+#define QSERDES_V3_RX_SIGDET_CNTRL 0x104
+#define QSERDES_V3_RX_SIGDET_LVL 0x108
+#define QSERDES_V3_RX_SIGDET_DEGLITCH_CNTRL 0x10c
+#define QSERDES_V3_RX_RX_BAND 0x110
+#define QSERDES_V3_RX_RX_INTERFACE_MODE 0x11c
+#define QSERDES_V3_RX_RX_MODE_00 0x164
+
+/* Only for QMP V3 PHY - PCS registers */
+#define QPHY_V3_PCS_POWER_DOWN_CONTROL 0x004
+#define QPHY_V3_PCS_TXMGN_V0 0x00c
+#define QPHY_V3_PCS_TXMGN_V1 0x010
+#define QPHY_V3_PCS_TXMGN_V2 0x014
+#define QPHY_V3_PCS_TXMGN_V3 0x018
+#define QPHY_V3_PCS_TXMGN_V4 0x01c
+#define QPHY_V3_PCS_TXMGN_LS 0x020
+#define QPHY_V3_PCS_TXDEEMPH_M6DB_V0 0x024
+#define QPHY_V3_PCS_TXDEEMPH_M3P5DB_V0 0x028
+#define QPHY_V3_PCS_TXDEEMPH_M6DB_V1 0x02c
+#define QPHY_V3_PCS_TXDEEMPH_M3P5DB_V1 0x030
+#define QPHY_V3_PCS_TXDEEMPH_M6DB_V2 0x034
+#define QPHY_V3_PCS_TXDEEMPH_M3P5DB_V2 0x038
+#define QPHY_V3_PCS_TXDEEMPH_M6DB_V3 0x03c
+#define QPHY_V3_PCS_TXDEEMPH_M3P5DB_V3 0x040
+#define QPHY_V3_PCS_TXDEEMPH_M6DB_V4 0x044
+#define QPHY_V3_PCS_TXDEEMPH_M3P5DB_V4 0x048
+#define QPHY_V3_PCS_TXDEEMPH_M6DB_LS 0x04c
+#define QPHY_V3_PCS_TXDEEMPH_M3P5DB_LS 0x050
+#define QPHY_V3_PCS_ENDPOINT_REFCLK_DRIVE 0x054
+#define QPHY_V3_PCS_RX_IDLE_DTCT_CNTRL 0x058
+#define QPHY_V3_PCS_RATE_SLEW_CNTRL 0x05c
+#define QPHY_V3_PCS_POWER_STATE_CONFIG1 0x060
+#define QPHY_V3_PCS_POWER_STATE_CONFIG2 0x064
+#define QPHY_V3_PCS_POWER_STATE_CONFIG4 0x06c
+#define QPHY_V3_PCS_RCVR_DTCT_DLY_P1U2_L 0x070
+#define QPHY_V3_PCS_RCVR_DTCT_DLY_P1U2_H 0x074
+#define QPHY_V3_PCS_RCVR_DTCT_DLY_U3_L 0x078
+#define QPHY_V3_PCS_RCVR_DTCT_DLY_U3_H 0x07c
+#define QPHY_V3_PCS_LOCK_DETECT_CONFIG1 0x080
+#define QPHY_V3_PCS_LOCK_DETECT_CONFIG2 0x084
+#define QPHY_V3_PCS_LOCK_DETECT_CONFIG3 0x088
+#define QPHY_V3_PCS_TSYNC_RSYNC_TIME 0x08c
+#define QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK 0x0a0
+#define QPHY_V3_PCS_LP_WAKEUP_DLY_TIME_AUXCLK 0x0a4
+#define QPHY_V3_PCS_LFPS_TX_ECSTART_EQTLOCK 0x0b0
+#define QPHY_V3_PCS_RXEQTRAINING_WAIT_TIME 0x0b8
+#define QPHY_V3_PCS_RXEQTRAINING_RUN_TIME 0x0bc
+#define QPHY_V3_PCS_FLL_CNTRL1 0x0c4
+#define QPHY_V3_PCS_FLL_CNTRL2 0x0c8
+#define QPHY_V3_PCS_FLL_CNT_VAL_L 0x0cc
+#define QPHY_V3_PCS_FLL_CNT_VAL_H_TOL 0x0d0
+#define QPHY_V3_PCS_FLL_MAN_CODE 0x0d4
+#define QPHY_V3_PCS_RX_SIGDET_LVL 0x1d8
+#define QPHY_V3_PCS_REFGEN_REQ_CONFIG1 0x20c
+#define QPHY_V3_PCS_REFGEN_REQ_CONFIG2 0x210
+
+/* Only for QMP V3 PHY - PCS_MISC registers */
+#define QPHY_V3_PCS_MISC_CLAMP_ENABLE 0x0c
+
+#endif
diff --git a/drivers/phy/qualcomm/phy-qcom-qusb2.c b/drivers/phy/qualcomm/phy-qcom-qusb2.c
new file mode 100644
index 000000000..901f525c8
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-qusb2.c
@@ -0,0 +1,897 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/phy/phy-qcom-qusb2.h>
+
+#define QUSB2PHY_PLL_TEST 0x04
+#define CLK_REF_SEL BIT(7)
+
+#define QUSB2PHY_PLL_TUNE 0x08
+#define QUSB2PHY_PLL_USER_CTL1 0x0c
+#define QUSB2PHY_PLL_USER_CTL2 0x10
+#define QUSB2PHY_PLL_AUTOPGM_CTL1 0x1c
+#define QUSB2PHY_PLL_PWR_CTRL 0x18
+
+/* QUSB2PHY_PLL_STATUS register bits */
+#define PLL_LOCKED BIT(5)
+
+/* QUSB2PHY_PLL_COMMON_STATUS_ONE register bits */
+#define CORE_READY_STATUS BIT(0)
+
+/* QUSB2PHY_PORT_POWERDOWN register bits */
+#define CLAMP_N_EN BIT(5)
+#define FREEZIO_N BIT(1)
+#define POWER_DOWN BIT(0)
+
+/* QUSB2PHY_PWR_CTRL1 register bits */
+#define PWR_CTRL1_VREF_SUPPLY_TRIM BIT(5)
+#define PWR_CTRL1_CLAMP_N_EN BIT(1)
+
+#define QUSB2PHY_REFCLK_ENABLE BIT(0)
+
+#define PHY_CLK_SCHEME_SEL BIT(0)
+
+/* QUSB2PHY_INTR_CTRL register bits */
+#define DMSE_INTR_HIGH_SEL BIT(4)
+#define DPSE_INTR_HIGH_SEL BIT(3)
+#define CHG_DET_INTR_EN BIT(2)
+#define DMSE_INTR_EN BIT(1)
+#define DPSE_INTR_EN BIT(0)
+
+/* QUSB2PHY_PLL_CORE_INPUT_OVERRIDE register bits */
+#define CORE_PLL_EN_FROM_RESET BIT(4)
+#define CORE_RESET BIT(5)
+#define CORE_RESET_MUX BIT(6)
+
+/* QUSB2PHY_IMP_CTRL1 register bits */
+#define IMP_RES_OFFSET_MASK GENMASK(5, 0)
+#define IMP_RES_OFFSET_SHIFT 0x0
+
+/* QUSB2PHY_PORT_TUNE1 register bits */
+#define HSTX_TRIM_MASK GENMASK(7, 4)
+#define HSTX_TRIM_SHIFT 0x4
+#define PREEMPH_WIDTH_HALF_BIT BIT(2)
+#define PREEMPHASIS_EN_MASK GENMASK(1, 0)
+#define PREEMPHASIS_EN_SHIFT 0x0
+
+#define QUSB2PHY_PLL_ANALOG_CONTROLS_TWO 0x04
+#define QUSB2PHY_PLL_CLOCK_INVERTERS 0x18c
+#define QUSB2PHY_PLL_CMODE 0x2c
+#define QUSB2PHY_PLL_LOCK_DELAY 0x184
+#define QUSB2PHY_PLL_DIGITAL_TIMERS_TWO 0xb4
+#define QUSB2PHY_PLL_BIAS_CONTROL_1 0x194
+#define QUSB2PHY_PLL_BIAS_CONTROL_2 0x198
+#define QUSB2PHY_PWR_CTRL2 0x214
+#define QUSB2PHY_IMP_CTRL1 0x220
+#define QUSB2PHY_IMP_CTRL2 0x224
+#define QUSB2PHY_CHG_CTRL2 0x23c
+
+struct qusb2_phy_init_tbl {
+ unsigned int offset;
+ unsigned int val;
+ /*
+ * register part of layout ?
+ * if yes, then offset gives index in the reg-layout
+ */
+ int in_layout;
+};
+
+#define QUSB2_PHY_INIT_CFG(o, v) \
+ { \
+ .offset = o, \
+ .val = v, \
+ }
+
+#define QUSB2_PHY_INIT_CFG_L(o, v) \
+ { \
+ .offset = o, \
+ .val = v, \
+ .in_layout = 1, \
+ }
+
+/* set of registers with offsets different per-PHY */
+enum qusb2phy_reg_layout {
+ QUSB2PHY_PLL_CORE_INPUT_OVERRIDE,
+ QUSB2PHY_PLL_STATUS,
+ QUSB2PHY_PORT_TUNE1,
+ QUSB2PHY_PORT_TUNE2,
+ QUSB2PHY_PORT_TUNE3,
+ QUSB2PHY_PORT_TUNE4,
+ QUSB2PHY_PORT_TUNE5,
+ QUSB2PHY_PORT_TEST1,
+ QUSB2PHY_PORT_TEST2,
+ QUSB2PHY_PORT_POWERDOWN,
+ QUSB2PHY_INTR_CTRL,
+};
+
+static const unsigned int msm8996_regs_layout[] = {
+ [QUSB2PHY_PLL_STATUS] = 0x38,
+ [QUSB2PHY_PORT_TUNE1] = 0x80,
+ [QUSB2PHY_PORT_TUNE2] = 0x84,
+ [QUSB2PHY_PORT_TUNE3] = 0x88,
+ [QUSB2PHY_PORT_TUNE4] = 0x8c,
+ [QUSB2PHY_PORT_TUNE5] = 0x90,
+ [QUSB2PHY_PORT_TEST1] = 0xb8,
+ [QUSB2PHY_PORT_TEST2] = 0x9c,
+ [QUSB2PHY_PORT_POWERDOWN] = 0xb4,
+ [QUSB2PHY_INTR_CTRL] = 0xbc,
+};
+
+static const struct qusb2_phy_init_tbl msm8996_init_tbl[] = {
+ QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE1, 0xf8),
+ QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE2, 0xb3),
+ QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE3, 0x83),
+ QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE4, 0xc0),
+
+ QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_TUNE, 0x30),
+ QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_USER_CTL1, 0x79),
+ QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_USER_CTL2, 0x21),
+
+ QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TEST2, 0x14),
+
+ QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_AUTOPGM_CTL1, 0x9f),
+ QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_PWR_CTRL, 0x00),
+};
+
+static const unsigned int sdm845_regs_layout[] = {
+ [QUSB2PHY_PLL_CORE_INPUT_OVERRIDE] = 0xa8,
+ [QUSB2PHY_PLL_STATUS] = 0x1a0,
+ [QUSB2PHY_PORT_TUNE1] = 0x240,
+ [QUSB2PHY_PORT_TUNE2] = 0x244,
+ [QUSB2PHY_PORT_TUNE3] = 0x248,
+ [QUSB2PHY_PORT_TUNE4] = 0x24c,
+ [QUSB2PHY_PORT_TUNE5] = 0x250,
+ [QUSB2PHY_PORT_TEST1] = 0x254,
+ [QUSB2PHY_PORT_TEST2] = 0x258,
+ [QUSB2PHY_PORT_POWERDOWN] = 0x210,
+ [QUSB2PHY_INTR_CTRL] = 0x230,
+};
+
+static const struct qusb2_phy_init_tbl sdm845_init_tbl[] = {
+ QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_ANALOG_CONTROLS_TWO, 0x03),
+ QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_CLOCK_INVERTERS, 0x7c),
+ QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_CMODE, 0x80),
+ QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_LOCK_DELAY, 0x0a),
+ QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_DIGITAL_TIMERS_TWO, 0x19),
+ QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_BIAS_CONTROL_1, 0x40),
+ QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_BIAS_CONTROL_2, 0x20),
+ QUSB2_PHY_INIT_CFG(QUSB2PHY_PWR_CTRL2, 0x21),
+ QUSB2_PHY_INIT_CFG(QUSB2PHY_IMP_CTRL1, 0x0),
+ QUSB2_PHY_INIT_CFG(QUSB2PHY_IMP_CTRL2, 0x58),
+
+ QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE1, 0x30),
+ QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE2, 0x29),
+ QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE3, 0xca),
+ QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE4, 0x04),
+ QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE5, 0x03),
+
+ QUSB2_PHY_INIT_CFG(QUSB2PHY_CHG_CTRL2, 0x0),
+};
+
+struct qusb2_phy_cfg {
+ const struct qusb2_phy_init_tbl *tbl;
+ /* number of entries in the table */
+ unsigned int tbl_num;
+ /* offset to PHY_CLK_SCHEME register in TCSR map */
+ unsigned int clk_scheme_offset;
+
+ /* array of registers with different offsets */
+ const unsigned int *regs;
+ unsigned int mask_core_ready;
+ unsigned int disable_ctrl;
+ unsigned int autoresume_en;
+
+ /* true if PHY has PLL_TEST register to select clk_scheme */
+ bool has_pll_test;
+
+ /* true if TUNE1 register must be updated by fused value, else TUNE2 */
+ bool update_tune1_with_efuse;
+
+ /* true if PHY has PLL_CORE_INPUT_OVERRIDE register to reset PLL */
+ bool has_pll_override;
+};
+
+static const struct qusb2_phy_cfg msm8996_phy_cfg = {
+ .tbl = msm8996_init_tbl,
+ .tbl_num = ARRAY_SIZE(msm8996_init_tbl),
+ .regs = msm8996_regs_layout,
+
+ .has_pll_test = true,
+ .disable_ctrl = (CLAMP_N_EN | FREEZIO_N | POWER_DOWN),
+ .mask_core_ready = PLL_LOCKED,
+ .autoresume_en = BIT(3),
+};
+
+static const struct qusb2_phy_cfg sdm845_phy_cfg = {
+ .tbl = sdm845_init_tbl,
+ .tbl_num = ARRAY_SIZE(sdm845_init_tbl),
+ .regs = sdm845_regs_layout,
+
+ .disable_ctrl = (PWR_CTRL1_VREF_SUPPLY_TRIM | PWR_CTRL1_CLAMP_N_EN |
+ POWER_DOWN),
+ .mask_core_ready = CORE_READY_STATUS,
+ .has_pll_override = true,
+ .autoresume_en = BIT(0),
+ .update_tune1_with_efuse = true,
+};
+
+static const char * const qusb2_phy_vreg_names[] = {
+ "vdda-pll", "vdda-phy-dpdm",
+};
+
+#define QUSB2_NUM_VREGS ARRAY_SIZE(qusb2_phy_vreg_names)
+
+/**
+ * struct qusb2_phy - structure holding qusb2 phy attributes
+ *
+ * @phy: generic phy
+ * @base: iomapped memory space for qubs2 phy
+ *
+ * @cfg_ahb_clk: AHB2PHY interface clock
+ * @ref_clk: phy reference clock
+ * @iface_clk: phy interface clock
+ * @phy_reset: phy reset control
+ * @vregs: regulator supplies bulk data
+ *
+ * @tcsr: TCSR syscon register map
+ * @cell: nvmem cell containing phy tuning value
+ *
+ * @override_imp_res_offset: PHY should use different rescode offset
+ * @imp_res_offset_value: rescode offset to be updated in IMP_CTRL1 register
+ * @override_hstx_trim: PHY should use different HSTX o/p current value
+ * @hstx_trim_value: HSTX_TRIM value to be updated in TUNE1 register
+ * @override_preemphasis: PHY should use different pre-amphasis amplitude
+ * @preemphasis_level: Amplitude Pre-Emphasis to be updated in TUNE1 register
+ * @override_preemphasis_width: PHY should use different pre-emphasis duration
+ * @preemphasis_width: half/full-width Pre-Emphasis updated via TUNE1
+ *
+ * @cfg: phy config data
+ * @has_se_clk_scheme: indicate if PHY has single-ended ref clock scheme
+ * @phy_initialized: indicate if PHY has been initialized
+ * @mode: current PHY mode
+ */
+struct qusb2_phy {
+ struct phy *phy;
+ void __iomem *base;
+
+ struct clk *cfg_ahb_clk;
+ struct clk *ref_clk;
+ struct clk *iface_clk;
+ struct reset_control *phy_reset;
+ struct regulator_bulk_data vregs[QUSB2_NUM_VREGS];
+
+ struct regmap *tcsr;
+ struct nvmem_cell *cell;
+
+ bool override_imp_res_offset;
+ u8 imp_res_offset_value;
+ bool override_hstx_trim;
+ u8 hstx_trim_value;
+ bool override_preemphasis;
+ u8 preemphasis_level;
+ bool override_preemphasis_width;
+ u8 preemphasis_width;
+
+ const struct qusb2_phy_cfg *cfg;
+ bool has_se_clk_scheme;
+ bool phy_initialized;
+ enum phy_mode mode;
+};
+
+static inline void qusb2_write_mask(void __iomem *base, u32 offset,
+ u32 val, u32 mask)
+{
+ u32 reg;
+
+ reg = readl(base + offset);
+ reg &= ~mask;
+ reg |= val & mask;
+ writel(reg, base + offset);
+
+ /* Ensure above write is completed */
+ readl(base + offset);
+}
+
+static inline void qusb2_setbits(void __iomem *base, u32 offset, u32 val)
+{
+ u32 reg;
+
+ reg = readl(base + offset);
+ reg |= val;
+ writel(reg, base + offset);
+
+ /* Ensure above write is completed */
+ readl(base + offset);
+}
+
+static inline void qusb2_clrbits(void __iomem *base, u32 offset, u32 val)
+{
+ u32 reg;
+
+ reg = readl(base + offset);
+ reg &= ~val;
+ writel(reg, base + offset);
+
+ /* Ensure above write is completed */
+ readl(base + offset);
+}
+
+static inline
+void qcom_qusb2_phy_configure(void __iomem *base,
+ const unsigned int *regs,
+ const struct qusb2_phy_init_tbl tbl[], int num)
+{
+ int i;
+
+ for (i = 0; i < num; i++) {
+ if (tbl[i].in_layout)
+ writel(tbl[i].val, base + regs[tbl[i].offset]);
+ else
+ writel(tbl[i].val, base + tbl[i].offset);
+ }
+}
+
+/*
+ * Update board specific PHY tuning override values if specified from
+ * device tree.
+ */
+static void qusb2_phy_override_phy_params(struct qusb2_phy *qphy)
+{
+ const struct qusb2_phy_cfg *cfg = qphy->cfg;
+
+ if (qphy->override_imp_res_offset)
+ qusb2_write_mask(qphy->base, QUSB2PHY_IMP_CTRL1,
+ qphy->imp_res_offset_value << IMP_RES_OFFSET_SHIFT,
+ IMP_RES_OFFSET_MASK);
+
+ if (qphy->override_hstx_trim)
+ qusb2_write_mask(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE1],
+ qphy->hstx_trim_value << HSTX_TRIM_SHIFT,
+ HSTX_TRIM_MASK);
+
+ if (qphy->override_preemphasis)
+ qusb2_write_mask(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE1],
+ qphy->preemphasis_level << PREEMPHASIS_EN_SHIFT,
+ PREEMPHASIS_EN_MASK);
+
+ if (qphy->override_preemphasis_width) {
+ if (qphy->preemphasis_width ==
+ QUSB2_V2_PREEMPHASIS_WIDTH_HALF_BIT)
+ qusb2_setbits(qphy->base,
+ cfg->regs[QUSB2PHY_PORT_TUNE1],
+ PREEMPH_WIDTH_HALF_BIT);
+ else
+ qusb2_clrbits(qphy->base,
+ cfg->regs[QUSB2PHY_PORT_TUNE1],
+ PREEMPH_WIDTH_HALF_BIT);
+ }
+}
+
+/*
+ * Fetches HS Tx tuning value from nvmem and sets the
+ * QUSB2PHY_PORT_TUNE1/2 register.
+ * For error case, skip setting the value and use the default value.
+ */
+static void qusb2_phy_set_tune2_param(struct qusb2_phy *qphy)
+{
+ struct device *dev = &qphy->phy->dev;
+ const struct qusb2_phy_cfg *cfg = qphy->cfg;
+ u8 *val, hstx_trim;
+
+ /* efuse register is optional */
+ if (!qphy->cell)
+ return;
+
+ /*
+ * Read efuse register having TUNE2/1 parameter's high nibble.
+ * If efuse register shows value as 0x0 (indicating value is not
+ * fused), or if we fail to find a valid efuse register setting,
+ * then use default value for high nibble that we have already
+ * set while configuring the phy.
+ */
+ val = nvmem_cell_read(qphy->cell, NULL);
+ if (IS_ERR(val)) {
+ dev_dbg(dev, "failed to read a valid hs-tx trim value\n");
+ return;
+ }
+ hstx_trim = val[0];
+ kfree(val);
+ if (!hstx_trim) {
+ dev_dbg(dev, "failed to read a valid hs-tx trim value\n");
+ return;
+ }
+
+ /* Fused TUNE1/2 value is the higher nibble only */
+ if (cfg->update_tune1_with_efuse)
+ qusb2_write_mask(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE1],
+ hstx_trim << HSTX_TRIM_SHIFT, HSTX_TRIM_MASK);
+ else
+ qusb2_write_mask(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE2],
+ hstx_trim << HSTX_TRIM_SHIFT, HSTX_TRIM_MASK);
+}
+
+static int qusb2_phy_set_mode(struct phy *phy, enum phy_mode mode)
+{
+ struct qusb2_phy *qphy = phy_get_drvdata(phy);
+
+ qphy->mode = mode;
+
+ return 0;
+}
+
+static int __maybe_unused qusb2_phy_runtime_suspend(struct device *dev)
+{
+ struct qusb2_phy *qphy = dev_get_drvdata(dev);
+ const struct qusb2_phy_cfg *cfg = qphy->cfg;
+ u32 intr_mask;
+
+ dev_vdbg(dev, "Suspending QUSB2 Phy, mode:%d\n", qphy->mode);
+
+ if (!qphy->phy_initialized) {
+ dev_vdbg(dev, "PHY not initialized, bailing out\n");
+ return 0;
+ }
+
+ /*
+ * Enable DP/DM interrupts to detect line state changes based on current
+ * speed. In other words, enable the triggers _opposite_ of what the
+ * current D+/D- levels are e.g. if currently D+ high, D- low
+ * (HS 'J'/Suspend), configure the mask to trigger on D+ low OR D- high
+ */
+ intr_mask = DPSE_INTR_EN | DMSE_INTR_EN;
+ switch (qphy->mode) {
+ case PHY_MODE_USB_HOST_HS:
+ case PHY_MODE_USB_HOST_FS:
+ case PHY_MODE_USB_DEVICE_HS:
+ case PHY_MODE_USB_DEVICE_FS:
+ intr_mask |= DMSE_INTR_HIGH_SEL;
+ break;
+ case PHY_MODE_USB_HOST_LS:
+ case PHY_MODE_USB_DEVICE_LS:
+ intr_mask |= DPSE_INTR_HIGH_SEL;
+ break;
+ default:
+ /* No device connected, enable both DP/DM high interrupt */
+ intr_mask |= DMSE_INTR_HIGH_SEL;
+ intr_mask |= DPSE_INTR_HIGH_SEL;
+ break;
+ }
+
+ writel(intr_mask, qphy->base + cfg->regs[QUSB2PHY_INTR_CTRL]);
+
+ /* hold core PLL into reset */
+ if (cfg->has_pll_override) {
+ qusb2_setbits(qphy->base,
+ cfg->regs[QUSB2PHY_PLL_CORE_INPUT_OVERRIDE],
+ CORE_PLL_EN_FROM_RESET | CORE_RESET |
+ CORE_RESET_MUX);
+ }
+
+ /* enable phy auto-resume only if device is connected on bus */
+ if (qphy->mode != PHY_MODE_INVALID) {
+ qusb2_setbits(qphy->base, cfg->regs[QUSB2PHY_PORT_TEST1],
+ cfg->autoresume_en);
+ /* Autoresume bit has to be toggled in order to enable it */
+ qusb2_clrbits(qphy->base, cfg->regs[QUSB2PHY_PORT_TEST1],
+ cfg->autoresume_en);
+ }
+
+ if (!qphy->has_se_clk_scheme)
+ clk_disable_unprepare(qphy->ref_clk);
+
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ clk_disable_unprepare(qphy->iface_clk);
+
+ return 0;
+}
+
+static int __maybe_unused qusb2_phy_runtime_resume(struct device *dev)
+{
+ struct qusb2_phy *qphy = dev_get_drvdata(dev);
+ const struct qusb2_phy_cfg *cfg = qphy->cfg;
+ int ret;
+
+ dev_vdbg(dev, "Resuming QUSB2 phy, mode:%d\n", qphy->mode);
+
+ if (!qphy->phy_initialized) {
+ dev_vdbg(dev, "PHY not initialized, bailing out\n");
+ return 0;
+ }
+
+ ret = clk_prepare_enable(qphy->iface_clk);
+ if (ret) {
+ dev_err(dev, "failed to enable iface_clk, %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(qphy->cfg_ahb_clk);
+ if (ret) {
+ dev_err(dev, "failed to enable cfg ahb clock, %d\n", ret);
+ goto disable_iface_clk;
+ }
+
+ if (!qphy->has_se_clk_scheme) {
+ ret = clk_prepare_enable(qphy->ref_clk);
+ if (ret) {
+ dev_err(dev, "failed to enable ref clk, %d\n", ret);
+ goto disable_ahb_clk;
+ }
+ }
+
+ writel(0x0, qphy->base + cfg->regs[QUSB2PHY_INTR_CTRL]);
+
+ /* bring core PLL out of reset */
+ if (cfg->has_pll_override) {
+ qusb2_clrbits(qphy->base,
+ cfg->regs[QUSB2PHY_PLL_CORE_INPUT_OVERRIDE],
+ CORE_RESET | CORE_RESET_MUX);
+ }
+
+ return 0;
+
+disable_ahb_clk:
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+disable_iface_clk:
+ clk_disable_unprepare(qphy->iface_clk);
+
+ return ret;
+}
+
+static int qusb2_phy_init(struct phy *phy)
+{
+ struct qusb2_phy *qphy = phy_get_drvdata(phy);
+ const struct qusb2_phy_cfg *cfg = qphy->cfg;
+ unsigned int val = 0;
+ unsigned int clk_scheme;
+ int ret;
+
+ dev_vdbg(&phy->dev, "%s(): Initializing QUSB2 phy\n", __func__);
+
+ /* turn on regulator supplies */
+ ret = regulator_bulk_enable(ARRAY_SIZE(qphy->vregs), qphy->vregs);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(qphy->iface_clk);
+ if (ret) {
+ dev_err(&phy->dev, "failed to enable iface_clk, %d\n", ret);
+ goto poweroff_phy;
+ }
+
+ /* enable ahb interface clock to program phy */
+ ret = clk_prepare_enable(qphy->cfg_ahb_clk);
+ if (ret) {
+ dev_err(&phy->dev, "failed to enable cfg ahb clock, %d\n", ret);
+ goto disable_iface_clk;
+ }
+
+ /* Perform phy reset */
+ ret = reset_control_assert(qphy->phy_reset);
+ if (ret) {
+ dev_err(&phy->dev, "failed to assert phy_reset, %d\n", ret);
+ goto disable_ahb_clk;
+ }
+
+ /* 100 us delay to keep PHY in reset mode */
+ usleep_range(100, 150);
+
+ ret = reset_control_deassert(qphy->phy_reset);
+ if (ret) {
+ dev_err(&phy->dev, "failed to de-assert phy_reset, %d\n", ret);
+ goto disable_ahb_clk;
+ }
+
+ /* Disable the PHY */
+ qusb2_setbits(qphy->base, cfg->regs[QUSB2PHY_PORT_POWERDOWN],
+ qphy->cfg->disable_ctrl);
+
+ if (cfg->has_pll_test) {
+ /* save reset value to override reference clock scheme later */
+ val = readl(qphy->base + QUSB2PHY_PLL_TEST);
+ }
+
+ qcom_qusb2_phy_configure(qphy->base, cfg->regs, cfg->tbl,
+ cfg->tbl_num);
+
+ /* Override board specific PHY tuning values */
+ qusb2_phy_override_phy_params(qphy);
+
+ /* Set efuse value for tuning the PHY */
+ qusb2_phy_set_tune2_param(qphy);
+
+ /* Enable the PHY */
+ qusb2_clrbits(qphy->base, cfg->regs[QUSB2PHY_PORT_POWERDOWN],
+ POWER_DOWN);
+
+ /* Required to get phy pll lock successfully */
+ usleep_range(150, 160);
+
+ /* Default is single-ended clock on msm8996 */
+ qphy->has_se_clk_scheme = true;
+ /*
+ * read TCSR_PHY_CLK_SCHEME register to check if single-ended
+ * clock scheme is selected. If yes, then disable differential
+ * ref_clk and use single-ended clock, otherwise use differential
+ * ref_clk only.
+ */
+ if (qphy->tcsr) {
+ ret = regmap_read(qphy->tcsr, qphy->cfg->clk_scheme_offset,
+ &clk_scheme);
+ if (ret) {
+ dev_err(&phy->dev, "failed to read clk scheme reg\n");
+ goto assert_phy_reset;
+ }
+
+ /* is it a differential clock scheme ? */
+ if (!(clk_scheme & PHY_CLK_SCHEME_SEL)) {
+ dev_vdbg(&phy->dev, "%s(): select differential clk\n",
+ __func__);
+ qphy->has_se_clk_scheme = false;
+ } else {
+ dev_vdbg(&phy->dev, "%s(): select single-ended clk\n",
+ __func__);
+ }
+ }
+
+ if (!qphy->has_se_clk_scheme) {
+ ret = clk_prepare_enable(qphy->ref_clk);
+ if (ret) {
+ dev_err(&phy->dev, "failed to enable ref clk, %d\n",
+ ret);
+ goto assert_phy_reset;
+ }
+ }
+
+ if (cfg->has_pll_test) {
+ if (!qphy->has_se_clk_scheme)
+ val &= ~CLK_REF_SEL;
+ else
+ val |= CLK_REF_SEL;
+
+ writel(val, qphy->base + QUSB2PHY_PLL_TEST);
+
+ /* ensure above write is through */
+ readl(qphy->base + QUSB2PHY_PLL_TEST);
+ }
+
+ /* Required to get phy pll lock successfully */
+ usleep_range(100, 110);
+
+ val = readb(qphy->base + cfg->regs[QUSB2PHY_PLL_STATUS]);
+ if (!(val & cfg->mask_core_ready)) {
+ dev_err(&phy->dev,
+ "QUSB2PHY pll lock failed: status reg = %x\n", val);
+ ret = -EBUSY;
+ goto disable_ref_clk;
+ }
+ qphy->phy_initialized = true;
+
+ return 0;
+
+disable_ref_clk:
+ if (!qphy->has_se_clk_scheme)
+ clk_disable_unprepare(qphy->ref_clk);
+assert_phy_reset:
+ reset_control_assert(qphy->phy_reset);
+disable_ahb_clk:
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+disable_iface_clk:
+ clk_disable_unprepare(qphy->iface_clk);
+poweroff_phy:
+ regulator_bulk_disable(ARRAY_SIZE(qphy->vregs), qphy->vregs);
+
+ return ret;
+}
+
+static int qusb2_phy_exit(struct phy *phy)
+{
+ struct qusb2_phy *qphy = phy_get_drvdata(phy);
+
+ /* Disable the PHY */
+ qusb2_setbits(qphy->base, qphy->cfg->regs[QUSB2PHY_PORT_POWERDOWN],
+ qphy->cfg->disable_ctrl);
+
+ if (!qphy->has_se_clk_scheme)
+ clk_disable_unprepare(qphy->ref_clk);
+
+ reset_control_assert(qphy->phy_reset);
+
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ clk_disable_unprepare(qphy->iface_clk);
+
+ regulator_bulk_disable(ARRAY_SIZE(qphy->vregs), qphy->vregs);
+
+ qphy->phy_initialized = false;
+
+ return 0;
+}
+
+static const struct phy_ops qusb2_phy_gen_ops = {
+ .init = qusb2_phy_init,
+ .exit = qusb2_phy_exit,
+ .set_mode = qusb2_phy_set_mode,
+ .owner = THIS_MODULE,
+};
+
+static const struct of_device_id qusb2_phy_of_match_table[] = {
+ {
+ .compatible = "qcom,msm8996-qusb2-phy",
+ .data = &msm8996_phy_cfg,
+ }, {
+ .compatible = "qcom,sdm845-qusb2-phy",
+ .data = &sdm845_phy_cfg,
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, qusb2_phy_of_match_table);
+
+static const struct dev_pm_ops qusb2_phy_pm_ops = {
+ SET_RUNTIME_PM_OPS(qusb2_phy_runtime_suspend,
+ qusb2_phy_runtime_resume, NULL)
+};
+
+static int qusb2_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct qusb2_phy *qphy;
+ struct phy_provider *phy_provider;
+ struct phy *generic_phy;
+ struct resource *res;
+ int ret, i;
+ int num;
+ u32 value;
+
+ qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL);
+ if (!qphy)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ qphy->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(qphy->base))
+ return PTR_ERR(qphy->base);
+
+ qphy->cfg_ahb_clk = devm_clk_get(dev, "cfg_ahb");
+ if (IS_ERR(qphy->cfg_ahb_clk)) {
+ ret = PTR_ERR(qphy->cfg_ahb_clk);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to get cfg ahb clk, %d\n", ret);
+ return ret;
+ }
+
+ qphy->ref_clk = devm_clk_get(dev, "ref");
+ if (IS_ERR(qphy->ref_clk)) {
+ ret = PTR_ERR(qphy->ref_clk);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to get ref clk, %d\n", ret);
+ return ret;
+ }
+
+ qphy->iface_clk = devm_clk_get(dev, "iface");
+ if (IS_ERR(qphy->iface_clk)) {
+ ret = PTR_ERR(qphy->iface_clk);
+ if (ret == -EPROBE_DEFER)
+ return ret;
+ qphy->iface_clk = NULL;
+ dev_dbg(dev, "failed to get iface clk, %d\n", ret);
+ }
+
+ qphy->phy_reset = devm_reset_control_get_by_index(&pdev->dev, 0);
+ if (IS_ERR(qphy->phy_reset)) {
+ dev_err(dev, "failed to get phy core reset\n");
+ return PTR_ERR(qphy->phy_reset);
+ }
+
+ num = ARRAY_SIZE(qphy->vregs);
+ for (i = 0; i < num; i++)
+ qphy->vregs[i].supply = qusb2_phy_vreg_names[i];
+
+ ret = devm_regulator_bulk_get(dev, num, qphy->vregs);
+ if (ret) {
+ dev_err(dev, "failed to get regulator supplies\n");
+ return ret;
+ }
+
+ /* Get the specific init parameters of QMP phy */
+ qphy->cfg = of_device_get_match_data(dev);
+
+ qphy->tcsr = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "qcom,tcsr-syscon");
+ if (IS_ERR(qphy->tcsr)) {
+ dev_dbg(dev, "failed to lookup TCSR regmap\n");
+ qphy->tcsr = NULL;
+ }
+
+ qphy->cell = devm_nvmem_cell_get(dev, NULL);
+ if (IS_ERR(qphy->cell)) {
+ if (PTR_ERR(qphy->cell) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ qphy->cell = NULL;
+ dev_dbg(dev, "failed to lookup tune2 hstx trim value\n");
+ }
+
+ if (!of_property_read_u32(dev->of_node, "qcom,imp-res-offset-value",
+ &value)) {
+ qphy->imp_res_offset_value = (u8)value;
+ qphy->override_imp_res_offset = true;
+ }
+
+ if (!of_property_read_u32(dev->of_node, "qcom,hstx-trim-value",
+ &value)) {
+ qphy->hstx_trim_value = (u8)value;
+ qphy->override_hstx_trim = true;
+ }
+
+ if (!of_property_read_u32(dev->of_node, "qcom,preemphasis-level",
+ &value)) {
+ qphy->preemphasis_level = (u8)value;
+ qphy->override_preemphasis = true;
+ }
+
+ if (!of_property_read_u32(dev->of_node, "qcom,preemphasis-width",
+ &value)) {
+ qphy->preemphasis_width = (u8)value;
+ qphy->override_preemphasis_width = true;
+ }
+
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ /*
+ * Prevent runtime pm from being ON by default. Users can enable
+ * it using power/control in sysfs.
+ */
+ pm_runtime_forbid(dev);
+
+ generic_phy = devm_phy_create(dev, NULL, &qusb2_phy_gen_ops);
+ if (IS_ERR(generic_phy)) {
+ ret = PTR_ERR(generic_phy);
+ dev_err(dev, "failed to create phy, %d\n", ret);
+ pm_runtime_disable(dev);
+ return ret;
+ }
+ qphy->phy = generic_phy;
+
+ dev_set_drvdata(dev, qphy);
+ phy_set_drvdata(generic_phy, qphy);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (!IS_ERR(phy_provider))
+ dev_info(dev, "Registered Qcom-QUSB2 phy\n");
+ else
+ pm_runtime_disable(dev);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static struct platform_driver qusb2_phy_driver = {
+ .probe = qusb2_phy_probe,
+ .driver = {
+ .name = "qcom-qusb2-phy",
+ .pm = &qusb2_phy_pm_ops,
+ .of_match_table = qusb2_phy_of_match_table,
+ },
+};
+
+module_platform_driver(qusb2_phy_driver);
+
+MODULE_AUTHOR("Vivek Gautam <vivek.gautam@codeaurora.org>");
+MODULE_DESCRIPTION("Qualcomm QUSB2 PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-i.h b/drivers/phy/qualcomm/phy-qcom-ufs-i.h
new file mode 100644
index 000000000..822c83b8e
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-ufs-i.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef UFS_QCOM_PHY_I_H_
+#define UFS_QCOM_PHY_I_H_
+
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/phy/phy-qcom-ufs.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+
+#define readl_poll_timeout(addr, val, cond, sleep_us, timeout_us) \
+({ \
+ ktime_t timeout = ktime_add_us(ktime_get(), timeout_us); \
+ might_sleep_if(timeout_us); \
+ for (;;) { \
+ (val) = readl(addr); \
+ if (cond) \
+ break; \
+ if (timeout_us && ktime_compare(ktime_get(), timeout) > 0) { \
+ (val) = readl(addr); \
+ break; \
+ } \
+ if (sleep_us) \
+ usleep_range(DIV_ROUND_UP(sleep_us, 4), sleep_us); \
+ } \
+ (cond) ? 0 : -ETIMEDOUT; \
+})
+
+#define UFS_QCOM_PHY_CAL_ENTRY(reg, val) \
+ { \
+ .reg_offset = reg, \
+ .cfg_value = val, \
+ }
+
+#define UFS_QCOM_PHY_NAME_LEN 30
+
+enum {
+ MASK_SERDES_START = 0x1,
+ MASK_PCS_READY = 0x1,
+};
+
+enum {
+ OFFSET_SERDES_START = 0x0,
+};
+
+struct ufs_qcom_phy_stored_attributes {
+ u32 att;
+ u32 value;
+};
+
+
+struct ufs_qcom_phy_calibration {
+ u32 reg_offset;
+ u32 cfg_value;
+};
+
+struct ufs_qcom_phy_vreg {
+ const char *name;
+ struct regulator *reg;
+ int max_uA;
+ int min_uV;
+ int max_uV;
+ bool enabled;
+};
+
+struct ufs_qcom_phy {
+ struct list_head list;
+ struct device *dev;
+ void __iomem *mmio;
+ void __iomem *dev_ref_clk_ctrl_mmio;
+ struct clk *tx_iface_clk;
+ struct clk *rx_iface_clk;
+ bool is_iface_clk_enabled;
+ struct clk *ref_clk_src;
+ struct clk *ref_clk_parent;
+ struct clk *ref_clk;
+ bool is_ref_clk_enabled;
+ bool is_dev_ref_clk_enabled;
+ struct ufs_qcom_phy_vreg vdda_pll;
+ struct ufs_qcom_phy_vreg vdda_phy;
+ struct ufs_qcom_phy_vreg vddp_ref_clk;
+ unsigned int quirks;
+
+ /*
+ * If UFS link is put into Hibern8 and if UFS PHY analog hardware is
+ * power collapsed (by clearing UFS_PHY_POWER_DOWN_CONTROL), Hibern8
+ * exit might fail even after powering on UFS PHY analog hardware.
+ * Enabling this quirk will help to solve above issue by doing
+ * custom PHY settings just before PHY analog power collapse.
+ */
+ #define UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE BIT(0)
+
+ u8 host_ctrl_rev_major;
+ u16 host_ctrl_rev_minor;
+ u16 host_ctrl_rev_step;
+
+ char name[UFS_QCOM_PHY_NAME_LEN];
+ struct ufs_qcom_phy_calibration *cached_regs;
+ int cached_regs_table_size;
+ bool is_powered_on;
+ bool is_started;
+ struct ufs_qcom_phy_specific_ops *phy_spec_ops;
+
+ enum phy_mode mode;
+};
+
+/**
+ * struct ufs_qcom_phy_specific_ops - set of pointers to functions which have a
+ * specific implementation per phy. Each UFS phy, should implement
+ * those functions according to its spec and requirements
+ * @start_serdes: pointer to a function that starts the serdes
+ * @is_physical_coding_sublayer_ready: pointer to a function that
+ * checks pcs readiness. returns 0 for success and non-zero for error.
+ * @set_tx_lane_enable: pointer to a function that enable tx lanes
+ * @power_control: pointer to a function that controls analog rail of phy
+ * and writes to QSERDES_RX_SIGDET_CNTRL attribute
+ */
+struct ufs_qcom_phy_specific_ops {
+ void (*start_serdes)(struct ufs_qcom_phy *phy);
+ int (*is_physical_coding_sublayer_ready)(struct ufs_qcom_phy *phy);
+ void (*set_tx_lane_enable)(struct ufs_qcom_phy *phy, u32 val);
+ void (*power_control)(struct ufs_qcom_phy *phy, bool val);
+};
+
+struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy);
+int ufs_qcom_phy_power_on(struct phy *generic_phy);
+int ufs_qcom_phy_power_off(struct phy *generic_phy);
+int ufs_qcom_phy_init_clks(struct ufs_qcom_phy *phy_common);
+int ufs_qcom_phy_init_vregulators(struct ufs_qcom_phy *phy_common);
+int ufs_qcom_phy_remove(struct phy *generic_phy,
+ struct ufs_qcom_phy *ufs_qcom_phy);
+struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
+ struct ufs_qcom_phy *common_cfg,
+ const struct phy_ops *ufs_qcom_phy_gen_ops,
+ struct ufs_qcom_phy_specific_ops *phy_spec_ops);
+int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
+ struct ufs_qcom_phy_calibration *tbl_A, int tbl_size_A,
+ struct ufs_qcom_phy_calibration *tbl_B, int tbl_size_B,
+ bool is_rate_B);
+#endif
diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c
new file mode 100644
index 000000000..ba1895b76
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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 "phy-qcom-ufs-qmp-14nm.h"
+
+#define UFS_PHY_NAME "ufs_phy_qmp_14nm"
+#define UFS_PHY_VDDA_PHY_UV (925000)
+
+static
+int ufs_qcom_phy_qmp_14nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
+ bool is_rate_B)
+{
+ int tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A);
+ int tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
+ int err;
+
+ err = ufs_qcom_phy_calibrate(ufs_qcom_phy, phy_cal_table_rate_A,
+ tbl_size_A, phy_cal_table_rate_B, tbl_size_B, is_rate_B);
+
+ if (err)
+ dev_err(ufs_qcom_phy->dev,
+ "%s: ufs_qcom_phy_calibrate() failed %d\n",
+ __func__, err);
+ return err;
+}
+
+static
+void ufs_qcom_phy_qmp_14nm_advertise_quirks(struct ufs_qcom_phy *phy_common)
+{
+ phy_common->quirks =
+ UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
+}
+
+static int ufs_qcom_phy_qmp_14nm_init(struct phy *generic_phy)
+{
+ struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
+ bool is_rate_B = false;
+ int ret;
+
+ if (phy_common->mode == PHY_MODE_UFS_HS_B)
+ is_rate_B = true;
+
+ ret = ufs_qcom_phy_qmp_14nm_phy_calibrate(phy_common, is_rate_B);
+ if (!ret)
+ /* phy calibrated, but yet to be started */
+ phy_common->is_started = false;
+
+ return ret;
+}
+
+static int ufs_qcom_phy_qmp_14nm_exit(struct phy *generic_phy)
+{
+ return 0;
+}
+
+static
+int ufs_qcom_phy_qmp_14nm_set_mode(struct phy *generic_phy, enum phy_mode mode)
+{
+ struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
+
+ phy_common->mode = PHY_MODE_INVALID;
+
+ if (mode > 0)
+ phy_common->mode = mode;
+
+ return 0;
+}
+
+static
+void ufs_qcom_phy_qmp_14nm_power_control(struct ufs_qcom_phy *phy, bool val)
+{
+ writel_relaxed(val ? 0x1 : 0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
+ /*
+ * Before any transactions involving PHY, ensure PHY knows
+ * that it's analog rail is powered ON (or OFF).
+ */
+ mb();
+}
+
+static inline
+void ufs_qcom_phy_qmp_14nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val)
+{
+ /*
+ * 14nm PHY does not have TX_LANE_ENABLE register.
+ * Implement this function so as not to propagate error to caller.
+ */
+}
+
+static inline void ufs_qcom_phy_qmp_14nm_start_serdes(struct ufs_qcom_phy *phy)
+{
+ u32 tmp;
+
+ tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
+ tmp &= ~MASK_SERDES_START;
+ tmp |= (1 << OFFSET_SERDES_START);
+ writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
+ /* Ensure register value is committed */
+ mb();
+}
+
+static int ufs_qcom_phy_qmp_14nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
+{
+ int err = 0;
+ u32 val;
+
+ err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS,
+ val, (val & MASK_PCS_READY), 10, 1000000);
+ if (err)
+ dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n",
+ __func__, err);
+ return err;
+}
+
+static const struct phy_ops ufs_qcom_phy_qmp_14nm_phy_ops = {
+ .init = ufs_qcom_phy_qmp_14nm_init,
+ .exit = ufs_qcom_phy_qmp_14nm_exit,
+ .power_on = ufs_qcom_phy_power_on,
+ .power_off = ufs_qcom_phy_power_off,
+ .set_mode = ufs_qcom_phy_qmp_14nm_set_mode,
+ .owner = THIS_MODULE,
+};
+
+static struct ufs_qcom_phy_specific_ops phy_14nm_ops = {
+ .start_serdes = ufs_qcom_phy_qmp_14nm_start_serdes,
+ .is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_14nm_is_pcs_ready,
+ .set_tx_lane_enable = ufs_qcom_phy_qmp_14nm_set_tx_lane_enable,
+ .power_control = ufs_qcom_phy_qmp_14nm_power_control,
+};
+
+static int ufs_qcom_phy_qmp_14nm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct phy *generic_phy;
+ struct ufs_qcom_phy_qmp_14nm *phy;
+ struct ufs_qcom_phy *phy_common;
+ int err = 0;
+
+ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy) {
+ err = -ENOMEM;
+ goto out;
+ }
+ phy_common = &phy->common_cfg;
+
+ generic_phy = ufs_qcom_phy_generic_probe(pdev, phy_common,
+ &ufs_qcom_phy_qmp_14nm_phy_ops, &phy_14nm_ops);
+
+ if (!generic_phy) {
+ err = -EIO;
+ goto out;
+ }
+
+ err = ufs_qcom_phy_init_clks(phy_common);
+ if (err)
+ goto out;
+
+ err = ufs_qcom_phy_init_vregulators(phy_common);
+ if (err)
+ goto out;
+
+ phy_common->vdda_phy.max_uV = UFS_PHY_VDDA_PHY_UV;
+ phy_common->vdda_phy.min_uV = UFS_PHY_VDDA_PHY_UV;
+
+ ufs_qcom_phy_qmp_14nm_advertise_quirks(phy_common);
+
+ phy_set_drvdata(generic_phy, phy);
+
+ strlcpy(phy_common->name, UFS_PHY_NAME, sizeof(phy_common->name));
+
+out:
+ return err;
+}
+
+static const struct of_device_id ufs_qcom_phy_qmp_14nm_of_match[] = {
+ {.compatible = "qcom,ufs-phy-qmp-14nm"},
+ {.compatible = "qcom,msm8996-ufs-phy-qmp-14nm"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_14nm_of_match);
+
+static struct platform_driver ufs_qcom_phy_qmp_14nm_driver = {
+ .probe = ufs_qcom_phy_qmp_14nm_probe,
+ .driver = {
+ .of_match_table = ufs_qcom_phy_qmp_14nm_of_match,
+ .name = "ufs_qcom_phy_qmp_14nm",
+ },
+};
+
+module_platform_driver(ufs_qcom_phy_qmp_14nm_driver);
+
+MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 14nm");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.h b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.h
new file mode 100644
index 000000000..3aefdbacb
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef UFS_QCOM_PHY_QMP_14NM_H_
+#define UFS_QCOM_PHY_QMP_14NM_H_
+
+#include "phy-qcom-ufs-i.h"
+
+/* QCOM UFS PHY control registers */
+#define COM_OFF(x) (0x000 + x)
+#define PHY_OFF(x) (0xC00 + x)
+#define TX_OFF(n, x) (0x400 + (0x400 * n) + x)
+#define RX_OFF(n, x) (0x600 + (0x400 * n) + x)
+
+/* UFS PHY QSERDES COM registers */
+#define QSERDES_COM_BG_TIMER COM_OFF(0x0C)
+#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN COM_OFF(0x34)
+#define QSERDES_COM_SYS_CLK_CTRL COM_OFF(0x3C)
+#define QSERDES_COM_LOCK_CMP1_MODE0 COM_OFF(0x4C)
+#define QSERDES_COM_LOCK_CMP2_MODE0 COM_OFF(0x50)
+#define QSERDES_COM_LOCK_CMP3_MODE0 COM_OFF(0x54)
+#define QSERDES_COM_LOCK_CMP1_MODE1 COM_OFF(0x58)
+#define QSERDES_COM_LOCK_CMP2_MODE1 COM_OFF(0x5C)
+#define QSERDES_COM_LOCK_CMP3_MODE1 COM_OFF(0x60)
+#define QSERDES_COM_CP_CTRL_MODE0 COM_OFF(0x78)
+#define QSERDES_COM_CP_CTRL_MODE1 COM_OFF(0x7C)
+#define QSERDES_COM_PLL_RCTRL_MODE0 COM_OFF(0x84)
+#define QSERDES_COM_PLL_RCTRL_MODE1 COM_OFF(0x88)
+#define QSERDES_COM_PLL_CCTRL_MODE0 COM_OFF(0x90)
+#define QSERDES_COM_PLL_CCTRL_MODE1 COM_OFF(0x94)
+#define QSERDES_COM_SYSCLK_EN_SEL COM_OFF(0xAC)
+#define QSERDES_COM_RESETSM_CNTRL COM_OFF(0xB4)
+#define QSERDES_COM_LOCK_CMP_EN COM_OFF(0xC8)
+#define QSERDES_COM_LOCK_CMP_CFG COM_OFF(0xCC)
+#define QSERDES_COM_DEC_START_MODE0 COM_OFF(0xD0)
+#define QSERDES_COM_DEC_START_MODE1 COM_OFF(0xD4)
+#define QSERDES_COM_DIV_FRAC_START1_MODE0 COM_OFF(0xDC)
+#define QSERDES_COM_DIV_FRAC_START2_MODE0 COM_OFF(0xE0)
+#define QSERDES_COM_DIV_FRAC_START3_MODE0 COM_OFF(0xE4)
+#define QSERDES_COM_DIV_FRAC_START1_MODE1 COM_OFF(0xE8)
+#define QSERDES_COM_DIV_FRAC_START2_MODE1 COM_OFF(0xEC)
+#define QSERDES_COM_DIV_FRAC_START3_MODE1 COM_OFF(0xF0)
+#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 COM_OFF(0x108)
+#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 COM_OFF(0x10C)
+#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1 COM_OFF(0x110)
+#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1 COM_OFF(0x114)
+#define QSERDES_COM_VCO_TUNE_CTRL COM_OFF(0x124)
+#define QSERDES_COM_VCO_TUNE_MAP COM_OFF(0x128)
+#define QSERDES_COM_VCO_TUNE1_MODE0 COM_OFF(0x12C)
+#define QSERDES_COM_VCO_TUNE2_MODE0 COM_OFF(0x130)
+#define QSERDES_COM_VCO_TUNE1_MODE1 COM_OFF(0x134)
+#define QSERDES_COM_VCO_TUNE2_MODE1 COM_OFF(0x138)
+#define QSERDES_COM_VCO_TUNE_TIMER1 COM_OFF(0x144)
+#define QSERDES_COM_VCO_TUNE_TIMER2 COM_OFF(0x148)
+#define QSERDES_COM_CLK_SELECT COM_OFF(0x174)
+#define QSERDES_COM_HSCLK_SEL COM_OFF(0x178)
+#define QSERDES_COM_CORECLK_DIV COM_OFF(0x184)
+#define QSERDES_COM_CORE_CLK_EN COM_OFF(0x18C)
+#define QSERDES_COM_CMN_CONFIG COM_OFF(0x194)
+#define QSERDES_COM_SVS_MODE_CLK_SEL COM_OFF(0x19C)
+#define QSERDES_COM_CORECLK_DIV_MODE1 COM_OFF(0x1BC)
+
+/* UFS PHY registers */
+#define UFS_PHY_PHY_START PHY_OFF(0x00)
+#define UFS_PHY_POWER_DOWN_CONTROL PHY_OFF(0x04)
+#define UFS_PHY_PCS_READY_STATUS PHY_OFF(0x168)
+
+/* UFS PHY TX registers */
+#define QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN TX_OFF(0, 0x68)
+#define QSERDES_TX_LANE_MODE TX_OFF(0, 0x94)
+
+/* UFS PHY RX registers */
+#define QSERDES_RX_UCDR_FASTLOCK_FO_GAIN RX_OFF(0, 0x40)
+#define QSERDES_RX_RX_TERM_BW RX_OFF(0, 0x90)
+#define QSERDES_RX_RX_EQ_GAIN1_LSB RX_OFF(0, 0xC4)
+#define QSERDES_RX_RX_EQ_GAIN1_MSB RX_OFF(0, 0xC8)
+#define QSERDES_RX_RX_EQ_GAIN2_LSB RX_OFF(0, 0xCC)
+#define QSERDES_RX_RX_EQ_GAIN2_MSB RX_OFF(0, 0xD0)
+#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2 RX_OFF(0, 0xD8)
+#define QSERDES_RX_SIGDET_CNTRL RX_OFF(0, 0x114)
+#define QSERDES_RX_SIGDET_LVL RX_OFF(0, 0x118)
+#define QSERDES_RX_SIGDET_DEGLITCH_CNTRL RX_OFF(0, 0x11C)
+#define QSERDES_RX_RX_INTERFACE_MODE RX_OFF(0, 0x12C)
+
+/*
+ * This structure represents the 14nm specific phy.
+ * common_cfg MUST remain the first field in this structure
+ * in case extra fields are added. This way, when calling
+ * get_ufs_qcom_phy() of generic phy, we can extract the
+ * common phy structure (struct ufs_qcom_phy) out of it
+ * regardless of the relevant specific phy.
+ */
+struct ufs_qcom_phy_qmp_14nm {
+ struct ufs_qcom_phy common_cfg;
+};
+
+static struct ufs_qcom_phy_calibration phy_cal_table_rate_A[] = {
+ UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CMN_CONFIG, 0x0e),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL, 0xd7),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CLK_SELECT, 0x30),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYS_CLK_CTRL, 0x06),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BG_TIMER, 0x0a),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_HSCLK_SEL, 0x05),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV, 0x0a),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV_MODE1, 0x0a),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_EN, 0x01),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_CTRL, 0x10),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x20),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORE_CLK_EN, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_CFG, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_TIMER1, 0xff),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_TIMER2, 0x3f),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x14),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SVS_MODE_CLK_SEL, 0x05),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE0, 0x82),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE0, 0x0b),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE0, 0x28),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE0, 0x28),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE0, 0x02),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE0, 0xff),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE0, 0x0c),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP3_MODE0, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE1, 0x98),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1_MODE1, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2_MODE1, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3_MODE1, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE1, 0x0b),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE1, 0x16),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE1, 0x28),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE1, 0x80),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE1, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE1, 0xd6),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE1, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE1, 0x32),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE1, 0x0f),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP3_MODE1, 0x00),
+
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN, 0x45),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE, 0x02),
+
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_LVL, 0x24),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_CNTRL, 0x02),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_INTERFACE_MODE, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x18),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_UCDR_FASTLOCK_FO_GAIN, 0x0B),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_TERM_BW, 0x5B),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB, 0xFF),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB, 0x3F),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB, 0xFF),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB, 0x0F),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0E),
+};
+
+static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x54),
+};
+
+#endif
diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c
new file mode 100644
index 000000000..49f435c71
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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 "phy-qcom-ufs-qmp-20nm.h"
+
+#define UFS_PHY_NAME "ufs_phy_qmp_20nm"
+
+static
+int ufs_qcom_phy_qmp_20nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
+ bool is_rate_B)
+{
+ struct ufs_qcom_phy_calibration *tbl_A, *tbl_B;
+ int tbl_size_A, tbl_size_B;
+ u8 major = ufs_qcom_phy->host_ctrl_rev_major;
+ u16 minor = ufs_qcom_phy->host_ctrl_rev_minor;
+ u16 step = ufs_qcom_phy->host_ctrl_rev_step;
+ int err;
+
+ if ((major == 0x1) && (minor == 0x002) && (step == 0x0000)) {
+ tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_2_0);
+ tbl_A = phy_cal_table_rate_A_1_2_0;
+ } else if ((major == 0x1) && (minor == 0x003) && (step == 0x0000)) {
+ tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_3_0);
+ tbl_A = phy_cal_table_rate_A_1_3_0;
+ } else {
+ dev_err(ufs_qcom_phy->dev, "%s: Unknown UFS-PHY version, no calibration values\n",
+ __func__);
+ err = -ENODEV;
+ goto out;
+ }
+
+ tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
+ tbl_B = phy_cal_table_rate_B;
+
+ err = ufs_qcom_phy_calibrate(ufs_qcom_phy, tbl_A, tbl_size_A,
+ tbl_B, tbl_size_B, is_rate_B);
+
+ if (err)
+ dev_err(ufs_qcom_phy->dev, "%s: ufs_qcom_phy_calibrate() failed %d\n",
+ __func__, err);
+
+out:
+ return err;
+}
+
+static
+void ufs_qcom_phy_qmp_20nm_advertise_quirks(struct ufs_qcom_phy *phy_common)
+{
+ phy_common->quirks =
+ UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
+}
+
+static int ufs_qcom_phy_qmp_20nm_init(struct phy *generic_phy)
+{
+ struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
+ bool is_rate_B = false;
+ int ret;
+
+ if (phy_common->mode == PHY_MODE_UFS_HS_B)
+ is_rate_B = true;
+
+ ret = ufs_qcom_phy_qmp_20nm_phy_calibrate(phy_common, is_rate_B);
+ if (!ret)
+ /* phy calibrated, but yet to be started */
+ phy_common->is_started = false;
+
+ return ret;
+}
+
+static int ufs_qcom_phy_qmp_20nm_exit(struct phy *generic_phy)
+{
+ return 0;
+}
+
+static
+int ufs_qcom_phy_qmp_20nm_set_mode(struct phy *generic_phy, enum phy_mode mode)
+{
+ struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
+
+ phy_common->mode = PHY_MODE_INVALID;
+
+ if (mode > 0)
+ phy_common->mode = mode;
+
+ return 0;
+}
+
+static
+void ufs_qcom_phy_qmp_20nm_power_control(struct ufs_qcom_phy *phy, bool val)
+{
+ bool hibern8_exit_after_pwr_collapse = phy->quirks &
+ UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
+
+ if (val) {
+ writel_relaxed(0x1, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
+ /*
+ * Before any transactions involving PHY, ensure PHY knows
+ * that it's analog rail is powered ON.
+ */
+ mb();
+
+ if (hibern8_exit_after_pwr_collapse) {
+ /*
+ * Give atleast 1us delay after restoring PHY analog
+ * power.
+ */
+ usleep_range(1, 2);
+ writel_relaxed(0x0A, phy->mmio +
+ QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
+ writel_relaxed(0x08, phy->mmio +
+ QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
+ /*
+ * Make sure workaround is deactivated before proceeding
+ * with normal PHY operations.
+ */
+ mb();
+ }
+ } else {
+ if (hibern8_exit_after_pwr_collapse) {
+ writel_relaxed(0x0A, phy->mmio +
+ QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
+ writel_relaxed(0x02, phy->mmio +
+ QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
+ /*
+ * Make sure that above workaround is activated before
+ * PHY analog power collapse.
+ */
+ mb();
+ }
+
+ writel_relaxed(0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
+ /*
+ * ensure that PHY knows its PHY analog rail is going
+ * to be powered down
+ */
+ mb();
+ }
+}
+
+static
+void ufs_qcom_phy_qmp_20nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val)
+{
+ writel_relaxed(val & UFS_PHY_TX_LANE_ENABLE_MASK,
+ phy->mmio + UFS_PHY_TX_LANE_ENABLE);
+ mb();
+}
+
+static inline void ufs_qcom_phy_qmp_20nm_start_serdes(struct ufs_qcom_phy *phy)
+{
+ u32 tmp;
+
+ tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
+ tmp &= ~MASK_SERDES_START;
+ tmp |= (1 << OFFSET_SERDES_START);
+ writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
+ mb();
+}
+
+static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
+{
+ int err = 0;
+ u32 val;
+
+ err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS,
+ val, (val & MASK_PCS_READY), 10, 1000000);
+ if (err)
+ dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n",
+ __func__, err);
+ return err;
+}
+
+static const struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
+ .init = ufs_qcom_phy_qmp_20nm_init,
+ .exit = ufs_qcom_phy_qmp_20nm_exit,
+ .power_on = ufs_qcom_phy_power_on,
+ .power_off = ufs_qcom_phy_power_off,
+ .set_mode = ufs_qcom_phy_qmp_20nm_set_mode,
+ .owner = THIS_MODULE,
+};
+
+static struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
+ .start_serdes = ufs_qcom_phy_qmp_20nm_start_serdes,
+ .is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_20nm_is_pcs_ready,
+ .set_tx_lane_enable = ufs_qcom_phy_qmp_20nm_set_tx_lane_enable,
+ .power_control = ufs_qcom_phy_qmp_20nm_power_control,
+};
+
+static int ufs_qcom_phy_qmp_20nm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct phy *generic_phy;
+ struct ufs_qcom_phy_qmp_20nm *phy;
+ struct ufs_qcom_phy *phy_common;
+ int err = 0;
+
+ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy) {
+ err = -ENOMEM;
+ goto out;
+ }
+ phy_common = &phy->common_cfg;
+
+ generic_phy = ufs_qcom_phy_generic_probe(pdev, phy_common,
+ &ufs_qcom_phy_qmp_20nm_phy_ops, &phy_20nm_ops);
+
+ if (!generic_phy) {
+ err = -EIO;
+ goto out;
+ }
+
+ err = ufs_qcom_phy_init_clks(phy_common);
+ if (err)
+ goto out;
+
+ err = ufs_qcom_phy_init_vregulators(phy_common);
+ if (err)
+ goto out;
+
+ ufs_qcom_phy_qmp_20nm_advertise_quirks(phy_common);
+
+ phy_set_drvdata(generic_phy, phy);
+
+ strlcpy(phy_common->name, UFS_PHY_NAME, sizeof(phy_common->name));
+
+out:
+ return err;
+}
+
+static const struct of_device_id ufs_qcom_phy_qmp_20nm_of_match[] = {
+ {.compatible = "qcom,ufs-phy-qmp-20nm"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_20nm_of_match);
+
+static struct platform_driver ufs_qcom_phy_qmp_20nm_driver = {
+ .probe = ufs_qcom_phy_qmp_20nm_probe,
+ .driver = {
+ .of_match_table = ufs_qcom_phy_qmp_20nm_of_match,
+ .name = "ufs_qcom_phy_qmp_20nm",
+ },
+};
+
+module_platform_driver(ufs_qcom_phy_qmp_20nm_driver);
+
+MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 20nm");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.h b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.h
new file mode 100644
index 000000000..4f3076bb3
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.h
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef UFS_QCOM_PHY_QMP_20NM_H_
+#define UFS_QCOM_PHY_QMP_20NM_H_
+
+#include "phy-qcom-ufs-i.h"
+
+/* QCOM UFS PHY control registers */
+
+#define COM_OFF(x) (0x000 + x)
+#define PHY_OFF(x) (0xC00 + x)
+#define TX_OFF(n, x) (0x400 + (0x400 * n) + x)
+#define RX_OFF(n, x) (0x600 + (0x400 * n) + x)
+
+/* UFS PHY PLL block registers */
+#define QSERDES_COM_SYS_CLK_CTRL COM_OFF(0x0)
+#define QSERDES_COM_PLL_VCOTAIL_EN COM_OFF(0x04)
+#define QSERDES_COM_PLL_CNTRL COM_OFF(0x14)
+#define QSERDES_COM_PLL_IP_SETI COM_OFF(0x24)
+#define QSERDES_COM_CORE_CLK_IN_SYNC_SEL COM_OFF(0x28)
+#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN COM_OFF(0x30)
+#define QSERDES_COM_PLL_CP_SETI COM_OFF(0x34)
+#define QSERDES_COM_PLL_IP_SETP COM_OFF(0x38)
+#define QSERDES_COM_PLL_CP_SETP COM_OFF(0x3C)
+#define QSERDES_COM_SYSCLK_EN_SEL_TXBAND COM_OFF(0x48)
+#define QSERDES_COM_RESETSM_CNTRL COM_OFF(0x4C)
+#define QSERDES_COM_RESETSM_CNTRL2 COM_OFF(0x50)
+#define QSERDES_COM_PLLLOCK_CMP1 COM_OFF(0x90)
+#define QSERDES_COM_PLLLOCK_CMP2 COM_OFF(0x94)
+#define QSERDES_COM_PLLLOCK_CMP3 COM_OFF(0x98)
+#define QSERDES_COM_PLLLOCK_CMP_EN COM_OFF(0x9C)
+#define QSERDES_COM_BGTC COM_OFF(0xA0)
+#define QSERDES_COM_DEC_START1 COM_OFF(0xAC)
+#define QSERDES_COM_PLL_AMP_OS COM_OFF(0xB0)
+#define QSERDES_COM_RES_CODE_UP_OFFSET COM_OFF(0xD8)
+#define QSERDES_COM_RES_CODE_DN_OFFSET COM_OFF(0xDC)
+#define QSERDES_COM_DIV_FRAC_START1 COM_OFF(0x100)
+#define QSERDES_COM_DIV_FRAC_START2 COM_OFF(0x104)
+#define QSERDES_COM_DIV_FRAC_START3 COM_OFF(0x108)
+#define QSERDES_COM_DEC_START2 COM_OFF(0x10C)
+#define QSERDES_COM_PLL_RXTXEPCLK_EN COM_OFF(0x110)
+#define QSERDES_COM_PLL_CRCTRL COM_OFF(0x114)
+#define QSERDES_COM_PLL_CLKEPDIV COM_OFF(0x118)
+
+/* TX LANE n (0, 1) registers */
+#define QSERDES_TX_EMP_POST1_LVL(n) TX_OFF(n, 0x08)
+#define QSERDES_TX_DRV_LVL(n) TX_OFF(n, 0x0C)
+#define QSERDES_TX_LANE_MODE(n) TX_OFF(n, 0x54)
+
+/* RX LANE n (0, 1) registers */
+#define QSERDES_RX_CDR_CONTROL1(n) RX_OFF(n, 0x0)
+#define QSERDES_RX_CDR_CONTROL_HALF(n) RX_OFF(n, 0x8)
+#define QSERDES_RX_RX_EQ_GAIN1_LSB(n) RX_OFF(n, 0xA8)
+#define QSERDES_RX_RX_EQ_GAIN1_MSB(n) RX_OFF(n, 0xAC)
+#define QSERDES_RX_RX_EQ_GAIN2_LSB(n) RX_OFF(n, 0xB0)
+#define QSERDES_RX_RX_EQ_GAIN2_MSB(n) RX_OFF(n, 0xB4)
+#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(n) RX_OFF(n, 0xBC)
+#define QSERDES_RX_CDR_CONTROL_QUARTER(n) RX_OFF(n, 0xC)
+#define QSERDES_RX_SIGDET_CNTRL(n) RX_OFF(n, 0x100)
+
+/* UFS PHY registers */
+#define UFS_PHY_PHY_START PHY_OFF(0x00)
+#define UFS_PHY_POWER_DOWN_CONTROL PHY_OFF(0x4)
+#define UFS_PHY_TX_LANE_ENABLE PHY_OFF(0x44)
+#define UFS_PHY_PWM_G1_CLK_DIVIDER PHY_OFF(0x08)
+#define UFS_PHY_PWM_G2_CLK_DIVIDER PHY_OFF(0x0C)
+#define UFS_PHY_PWM_G3_CLK_DIVIDER PHY_OFF(0x10)
+#define UFS_PHY_PWM_G4_CLK_DIVIDER PHY_OFF(0x14)
+#define UFS_PHY_CORECLK_PWM_G1_CLK_DIVIDER PHY_OFF(0x34)
+#define UFS_PHY_CORECLK_PWM_G2_CLK_DIVIDER PHY_OFF(0x38)
+#define UFS_PHY_CORECLK_PWM_G3_CLK_DIVIDER PHY_OFF(0x3C)
+#define UFS_PHY_CORECLK_PWM_G4_CLK_DIVIDER PHY_OFF(0x40)
+#define UFS_PHY_OMC_STATUS_RDVAL PHY_OFF(0x68)
+#define UFS_PHY_LINE_RESET_TIME PHY_OFF(0x28)
+#define UFS_PHY_LINE_RESET_GRANULARITY PHY_OFF(0x2C)
+#define UFS_PHY_TSYNC_RSYNC_CNTL PHY_OFF(0x48)
+#define UFS_PHY_PLL_CNTL PHY_OFF(0x50)
+#define UFS_PHY_TX_LARGE_AMP_DRV_LVL PHY_OFF(0x54)
+#define UFS_PHY_TX_SMALL_AMP_DRV_LVL PHY_OFF(0x5C)
+#define UFS_PHY_TX_LARGE_AMP_POST_EMP_LVL PHY_OFF(0x58)
+#define UFS_PHY_TX_SMALL_AMP_POST_EMP_LVL PHY_OFF(0x60)
+#define UFS_PHY_CFG_CHANGE_CNT_VAL PHY_OFF(0x64)
+#define UFS_PHY_RX_SYNC_WAIT_TIME PHY_OFF(0x6C)
+#define UFS_PHY_TX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xB4)
+#define UFS_PHY_RX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xE0)
+#define UFS_PHY_TX_MIN_STALL_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xB8)
+#define UFS_PHY_RX_MIN_STALL_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xE4)
+#define UFS_PHY_TX_MIN_SAVE_CONFIG_TIME_CAPABILITY PHY_OFF(0xBC)
+#define UFS_PHY_RX_MIN_SAVE_CONFIG_TIME_CAPABILITY PHY_OFF(0xE8)
+#define UFS_PHY_RX_PWM_BURST_CLOSURE_LENGTH_CAPABILITY PHY_OFF(0xFC)
+#define UFS_PHY_RX_MIN_ACTIVATETIME_CAPABILITY PHY_OFF(0x100)
+#define UFS_PHY_RX_SIGDET_CTRL3 PHY_OFF(0x14c)
+#define UFS_PHY_RMMI_ATTR_CTRL PHY_OFF(0x160)
+#define UFS_PHY_RMMI_RX_CFGUPDT_L1 (1 << 7)
+#define UFS_PHY_RMMI_TX_CFGUPDT_L1 (1 << 6)
+#define UFS_PHY_RMMI_CFGWR_L1 (1 << 5)
+#define UFS_PHY_RMMI_CFGRD_L1 (1 << 4)
+#define UFS_PHY_RMMI_RX_CFGUPDT_L0 (1 << 3)
+#define UFS_PHY_RMMI_TX_CFGUPDT_L0 (1 << 2)
+#define UFS_PHY_RMMI_CFGWR_L0 (1 << 1)
+#define UFS_PHY_RMMI_CFGRD_L0 (1 << 0)
+#define UFS_PHY_RMMI_ATTRID PHY_OFF(0x164)
+#define UFS_PHY_RMMI_ATTRWRVAL PHY_OFF(0x168)
+#define UFS_PHY_RMMI_ATTRRDVAL_L0_STATUS PHY_OFF(0x16C)
+#define UFS_PHY_RMMI_ATTRRDVAL_L1_STATUS PHY_OFF(0x170)
+#define UFS_PHY_PCS_READY_STATUS PHY_OFF(0x174)
+
+#define UFS_PHY_TX_LANE_ENABLE_MASK 0x3
+
+/*
+ * This structure represents the 20nm specific phy.
+ * common_cfg MUST remain the first field in this structure
+ * in case extra fields are added. This way, when calling
+ * get_ufs_qcom_phy() of generic phy, we can extract the
+ * common phy structure (struct ufs_qcom_phy) out of it
+ * regardless of the relevant specific phy.
+ */
+struct ufs_qcom_phy_qmp_20nm {
+ struct ufs_qcom_phy common_cfg;
+};
+
+static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_2_0[] = {
+ UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
+ UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x3f),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x1b),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x0f),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(0), 0x2F),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(0), 0x20),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(1), 0x2F),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(1), 0x20),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3),
+};
+
+static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_3_0[] = {
+ UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
+ UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x2b),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x38),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x3c),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_UP_OFFSET, 0x02),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_DN_OFFSET, 0x02),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CNTRL, 0x40),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3),
+};
+
+static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x98),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0x65),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x1e),
+};
+
+#endif
diff --git a/drivers/phy/qualcomm/phy-qcom-ufs.c b/drivers/phy/qualcomm/phy-qcom-ufs.c
new file mode 100644
index 000000000..c5493ea51
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-ufs.c
@@ -0,0 +1,682 @@
+/*
+ * Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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 "phy-qcom-ufs-i.h"
+
+#define MAX_PROP_NAME 32
+#define VDDA_PHY_MIN_UV 1000000
+#define VDDA_PHY_MAX_UV 1000000
+#define VDDA_PLL_MIN_UV 1800000
+#define VDDA_PLL_MAX_UV 1800000
+#define VDDP_REF_CLK_MIN_UV 1200000
+#define VDDP_REF_CLK_MAX_UV 1200000
+
+int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
+ struct ufs_qcom_phy_calibration *tbl_A,
+ int tbl_size_A,
+ struct ufs_qcom_phy_calibration *tbl_B,
+ int tbl_size_B, bool is_rate_B)
+{
+ int i;
+ int ret = 0;
+
+ if (!tbl_A) {
+ dev_err(ufs_qcom_phy->dev, "%s: tbl_A is NULL", __func__);
+ ret = EINVAL;
+ goto out;
+ }
+
+ for (i = 0; i < tbl_size_A; i++)
+ writel_relaxed(tbl_A[i].cfg_value,
+ ufs_qcom_phy->mmio + tbl_A[i].reg_offset);
+
+ /*
+ * In case we would like to work in rate B, we need
+ * to override a registers that were configured in rate A table
+ * with registers of rate B table.
+ * table.
+ */
+ if (is_rate_B) {
+ if (!tbl_B) {
+ dev_err(ufs_qcom_phy->dev, "%s: tbl_B is NULL",
+ __func__);
+ ret = EINVAL;
+ goto out;
+ }
+
+ for (i = 0; i < tbl_size_B; i++)
+ writel_relaxed(tbl_B[i].cfg_value,
+ ufs_qcom_phy->mmio + tbl_B[i].reg_offset);
+ }
+
+ /* flush buffered writes */
+ mb();
+
+out:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_calibrate);
+
+/*
+ * This assumes the embedded phy structure inside generic_phy is of type
+ * struct ufs_qcom_phy. In order to function properly it's crucial
+ * to keep the embedded struct "struct ufs_qcom_phy common_cfg"
+ * as the first inside generic_phy.
+ */
+struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy)
+{
+ return (struct ufs_qcom_phy *)phy_get_drvdata(generic_phy);
+}
+EXPORT_SYMBOL_GPL(get_ufs_qcom_phy);
+
+static
+int ufs_qcom_phy_base_init(struct platform_device *pdev,
+ struct ufs_qcom_phy *phy_common)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ int err = 0;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_mem");
+ phy_common->mmio = devm_ioremap_resource(dev, res);
+ if (IS_ERR((void const *)phy_common->mmio)) {
+ err = PTR_ERR((void const *)phy_common->mmio);
+ phy_common->mmio = NULL;
+ dev_err(dev, "%s: ioremap for phy_mem resource failed %d\n",
+ __func__, err);
+ return err;
+ }
+
+ /* "dev_ref_clk_ctrl_mem" is optional resource */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "dev_ref_clk_ctrl_mem");
+ phy_common->dev_ref_clk_ctrl_mmio = devm_ioremap_resource(dev, res);
+ if (IS_ERR((void const *)phy_common->dev_ref_clk_ctrl_mmio))
+ phy_common->dev_ref_clk_ctrl_mmio = NULL;
+
+ return 0;
+}
+
+struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
+ struct ufs_qcom_phy *common_cfg,
+ const struct phy_ops *ufs_qcom_phy_gen_ops,
+ struct ufs_qcom_phy_specific_ops *phy_spec_ops)
+{
+ int err;
+ struct device *dev = &pdev->dev;
+ struct phy *generic_phy = NULL;
+ struct phy_provider *phy_provider;
+
+ err = ufs_qcom_phy_base_init(pdev, common_cfg);
+ if (err) {
+ dev_err(dev, "%s: phy base init failed %d\n", __func__, err);
+ goto out;
+ }
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(phy_provider)) {
+ err = PTR_ERR(phy_provider);
+ dev_err(dev, "%s: failed to register phy %d\n", __func__, err);
+ goto out;
+ }
+
+ generic_phy = devm_phy_create(dev, NULL, ufs_qcom_phy_gen_ops);
+ if (IS_ERR(generic_phy)) {
+ err = PTR_ERR(generic_phy);
+ dev_err(dev, "%s: failed to create phy %d\n", __func__, err);
+ generic_phy = NULL;
+ goto out;
+ }
+
+ common_cfg->phy_spec_ops = phy_spec_ops;
+ common_cfg->dev = dev;
+
+out:
+ return generic_phy;
+}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_generic_probe);
+
+static int __ufs_qcom_phy_clk_get(struct device *dev,
+ const char *name, struct clk **clk_out, bool err_print)
+{
+ struct clk *clk;
+ int err = 0;
+
+ clk = devm_clk_get(dev, name);
+ if (IS_ERR(clk)) {
+ err = PTR_ERR(clk);
+ if (err_print)
+ dev_err(dev, "failed to get %s err %d", name, err);
+ } else {
+ *clk_out = clk;
+ }
+
+ return err;
+}
+
+static int ufs_qcom_phy_clk_get(struct device *dev,
+ const char *name, struct clk **clk_out)
+{
+ return __ufs_qcom_phy_clk_get(dev, name, clk_out, true);
+}
+
+int ufs_qcom_phy_init_clks(struct ufs_qcom_phy *phy_common)
+{
+ int err;
+
+ if (of_device_is_compatible(phy_common->dev->of_node,
+ "qcom,msm8996-ufs-phy-qmp-14nm"))
+ goto skip_txrx_clk;
+
+ err = ufs_qcom_phy_clk_get(phy_common->dev, "tx_iface_clk",
+ &phy_common->tx_iface_clk);
+ if (err)
+ goto out;
+
+ err = ufs_qcom_phy_clk_get(phy_common->dev, "rx_iface_clk",
+ &phy_common->rx_iface_clk);
+ if (err)
+ goto out;
+
+skip_txrx_clk:
+ err = ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk_src",
+ &phy_common->ref_clk_src);
+ if (err)
+ goto out;
+
+ /*
+ * "ref_clk_parent" is optional hence don't abort init if it's not
+ * found.
+ */
+ __ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk_parent",
+ &phy_common->ref_clk_parent, false);
+
+ err = ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk",
+ &phy_common->ref_clk);
+
+out:
+ return err;
+}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_init_clks);
+
+static int ufs_qcom_phy_init_vreg(struct device *dev,
+ struct ufs_qcom_phy_vreg *vreg,
+ const char *name)
+{
+ int err = 0;
+
+ char prop_name[MAX_PROP_NAME];
+
+ vreg->name = name;
+ vreg->reg = devm_regulator_get(dev, name);
+ if (IS_ERR(vreg->reg)) {
+ err = PTR_ERR(vreg->reg);
+ dev_err(dev, "failed to get %s, %d\n", name, err);
+ goto out;
+ }
+
+ if (dev->of_node) {
+ snprintf(prop_name, MAX_PROP_NAME, "%s-max-microamp", name);
+ err = of_property_read_u32(dev->of_node,
+ prop_name, &vreg->max_uA);
+ if (err && err != -EINVAL) {
+ dev_err(dev, "%s: failed to read %s\n",
+ __func__, prop_name);
+ goto out;
+ } else if (err == -EINVAL || !vreg->max_uA) {
+ if (regulator_count_voltages(vreg->reg) > 0) {
+ dev_err(dev, "%s: %s is mandatory\n",
+ __func__, prop_name);
+ goto out;
+ }
+ err = 0;
+ }
+ }
+
+ if (!strcmp(name, "vdda-pll")) {
+ vreg->max_uV = VDDA_PLL_MAX_UV;
+ vreg->min_uV = VDDA_PLL_MIN_UV;
+ } else if (!strcmp(name, "vdda-phy")) {
+ vreg->max_uV = VDDA_PHY_MAX_UV;
+ vreg->min_uV = VDDA_PHY_MIN_UV;
+ } else if (!strcmp(name, "vddp-ref-clk")) {
+ vreg->max_uV = VDDP_REF_CLK_MAX_UV;
+ vreg->min_uV = VDDP_REF_CLK_MIN_UV;
+ }
+
+out:
+ return err;
+}
+
+int ufs_qcom_phy_init_vregulators(struct ufs_qcom_phy *phy_common)
+{
+ int err;
+
+ err = ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vdda_pll,
+ "vdda-pll");
+ if (err)
+ goto out;
+
+ err = ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vdda_phy,
+ "vdda-phy");
+
+ if (err)
+ goto out;
+
+ err = ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vddp_ref_clk,
+ "vddp-ref-clk");
+
+out:
+ return err;
+}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_init_vregulators);
+
+static int ufs_qcom_phy_cfg_vreg(struct device *dev,
+ struct ufs_qcom_phy_vreg *vreg, bool on)
+{
+ int ret = 0;
+ struct regulator *reg = vreg->reg;
+ const char *name = vreg->name;
+ int min_uV;
+ int uA_load;
+
+ if (regulator_count_voltages(reg) > 0) {
+ min_uV = on ? vreg->min_uV : 0;
+ ret = regulator_set_voltage(reg, min_uV, vreg->max_uV);
+ if (ret) {
+ dev_err(dev, "%s: %s set voltage failed, err=%d\n",
+ __func__, name, ret);
+ goto out;
+ }
+ uA_load = on ? vreg->max_uA : 0;
+ ret = regulator_set_load(reg, uA_load);
+ if (ret >= 0) {
+ /*
+ * regulator_set_load() returns new regulator
+ * mode upon success.
+ */
+ ret = 0;
+ } else {
+ dev_err(dev, "%s: %s set optimum mode(uA_load=%d) failed, err=%d\n",
+ __func__, name, uA_load, ret);
+ goto out;
+ }
+ }
+out:
+ return ret;
+}
+
+static int ufs_qcom_phy_enable_vreg(struct device *dev,
+ struct ufs_qcom_phy_vreg *vreg)
+{
+ int ret = 0;
+
+ if (!vreg || vreg->enabled)
+ goto out;
+
+ ret = ufs_qcom_phy_cfg_vreg(dev, vreg, true);
+ if (ret) {
+ dev_err(dev, "%s: ufs_qcom_phy_cfg_vreg() failed, err=%d\n",
+ __func__, ret);
+ goto out;
+ }
+
+ ret = regulator_enable(vreg->reg);
+ if (ret) {
+ dev_err(dev, "%s: enable failed, err=%d\n",
+ __func__, ret);
+ goto out;
+ }
+
+ vreg->enabled = true;
+out:
+ return ret;
+}
+
+static int ufs_qcom_phy_enable_ref_clk(struct ufs_qcom_phy *phy)
+{
+ int ret = 0;
+
+ if (phy->is_ref_clk_enabled)
+ goto out;
+
+ /*
+ * reference clock is propagated in a daisy-chained manner from
+ * source to phy, so ungate them at each stage.
+ */
+ ret = clk_prepare_enable(phy->ref_clk_src);
+ if (ret) {
+ dev_err(phy->dev, "%s: ref_clk_src enable failed %d\n",
+ __func__, ret);
+ goto out;
+ }
+
+ /*
+ * "ref_clk_parent" is optional clock hence make sure that clk reference
+ * is available before trying to enable the clock.
+ */
+ if (phy->ref_clk_parent) {
+ ret = clk_prepare_enable(phy->ref_clk_parent);
+ if (ret) {
+ dev_err(phy->dev, "%s: ref_clk_parent enable failed %d\n",
+ __func__, ret);
+ goto out_disable_src;
+ }
+ }
+
+ ret = clk_prepare_enable(phy->ref_clk);
+ if (ret) {
+ dev_err(phy->dev, "%s: ref_clk enable failed %d\n",
+ __func__, ret);
+ goto out_disable_parent;
+ }
+
+ phy->is_ref_clk_enabled = true;
+ goto out;
+
+out_disable_parent:
+ if (phy->ref_clk_parent)
+ clk_disable_unprepare(phy->ref_clk_parent);
+out_disable_src:
+ clk_disable_unprepare(phy->ref_clk_src);
+out:
+ return ret;
+}
+
+static int ufs_qcom_phy_disable_vreg(struct device *dev,
+ struct ufs_qcom_phy_vreg *vreg)
+{
+ int ret = 0;
+
+ if (!vreg || !vreg->enabled)
+ goto out;
+
+ ret = regulator_disable(vreg->reg);
+
+ if (!ret) {
+ /* ignore errors on applying disable config */
+ ufs_qcom_phy_cfg_vreg(dev, vreg, false);
+ vreg->enabled = false;
+ } else {
+ dev_err(dev, "%s: %s disable failed, err=%d\n",
+ __func__, vreg->name, ret);
+ }
+out:
+ return ret;
+}
+
+static void ufs_qcom_phy_disable_ref_clk(struct ufs_qcom_phy *phy)
+{
+ if (phy->is_ref_clk_enabled) {
+ clk_disable_unprepare(phy->ref_clk);
+ /*
+ * "ref_clk_parent" is optional clock hence make sure that clk
+ * reference is available before trying to disable the clock.
+ */
+ if (phy->ref_clk_parent)
+ clk_disable_unprepare(phy->ref_clk_parent);
+ clk_disable_unprepare(phy->ref_clk_src);
+ phy->is_ref_clk_enabled = false;
+ }
+}
+
+#define UFS_REF_CLK_EN (1 << 5)
+
+static void ufs_qcom_phy_dev_ref_clk_ctrl(struct phy *generic_phy, bool enable)
+{
+ struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
+
+ if (phy->dev_ref_clk_ctrl_mmio &&
+ (enable ^ phy->is_dev_ref_clk_enabled)) {
+ u32 temp = readl_relaxed(phy->dev_ref_clk_ctrl_mmio);
+
+ if (enable)
+ temp |= UFS_REF_CLK_EN;
+ else
+ temp &= ~UFS_REF_CLK_EN;
+
+ /*
+ * If we are here to disable this clock immediately after
+ * entering into hibern8, we need to make sure that device
+ * ref_clk is active atleast 1us after the hibern8 enter.
+ */
+ if (!enable)
+ udelay(1);
+
+ writel_relaxed(temp, phy->dev_ref_clk_ctrl_mmio);
+ /* ensure that ref_clk is enabled/disabled before we return */
+ wmb();
+ /*
+ * If we call hibern8 exit after this, we need to make sure that
+ * device ref_clk is stable for atleast 1us before the hibern8
+ * exit command.
+ */
+ if (enable)
+ udelay(1);
+
+ phy->is_dev_ref_clk_enabled = enable;
+ }
+}
+
+void ufs_qcom_phy_enable_dev_ref_clk(struct phy *generic_phy)
+{
+ ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, true);
+}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_enable_dev_ref_clk);
+
+void ufs_qcom_phy_disable_dev_ref_clk(struct phy *generic_phy)
+{
+ ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, false);
+}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_disable_dev_ref_clk);
+
+/* Turn ON M-PHY RMMI interface clocks */
+static int ufs_qcom_phy_enable_iface_clk(struct ufs_qcom_phy *phy)
+{
+ int ret = 0;
+
+ if (phy->is_iface_clk_enabled)
+ goto out;
+
+ ret = clk_prepare_enable(phy->tx_iface_clk);
+ if (ret) {
+ dev_err(phy->dev, "%s: tx_iface_clk enable failed %d\n",
+ __func__, ret);
+ goto out;
+ }
+ ret = clk_prepare_enable(phy->rx_iface_clk);
+ if (ret) {
+ clk_disable_unprepare(phy->tx_iface_clk);
+ dev_err(phy->dev, "%s: rx_iface_clk enable failed %d. disabling also tx_iface_clk\n",
+ __func__, ret);
+ goto out;
+ }
+ phy->is_iface_clk_enabled = true;
+
+out:
+ return ret;
+}
+
+/* Turn OFF M-PHY RMMI interface clocks */
+void ufs_qcom_phy_disable_iface_clk(struct ufs_qcom_phy *phy)
+{
+ if (phy->is_iface_clk_enabled) {
+ clk_disable_unprepare(phy->tx_iface_clk);
+ clk_disable_unprepare(phy->rx_iface_clk);
+ phy->is_iface_clk_enabled = false;
+ }
+}
+
+static int ufs_qcom_phy_start_serdes(struct ufs_qcom_phy *ufs_qcom_phy)
+{
+ int ret = 0;
+
+ if (!ufs_qcom_phy->phy_spec_ops->start_serdes) {
+ dev_err(ufs_qcom_phy->dev, "%s: start_serdes() callback is not supported\n",
+ __func__);
+ ret = -ENOTSUPP;
+ } else {
+ ufs_qcom_phy->phy_spec_ops->start_serdes(ufs_qcom_phy);
+ }
+
+ return ret;
+}
+
+int ufs_qcom_phy_set_tx_lane_enable(struct phy *generic_phy, u32 tx_lanes)
+{
+ struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+ int ret = 0;
+
+ if (!ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable) {
+ dev_err(ufs_qcom_phy->dev, "%s: set_tx_lane_enable() callback is not supported\n",
+ __func__);
+ ret = -ENOTSUPP;
+ } else {
+ ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable(ufs_qcom_phy,
+ tx_lanes);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_set_tx_lane_enable);
+
+void ufs_qcom_phy_save_controller_version(struct phy *generic_phy,
+ u8 major, u16 minor, u16 step)
+{
+ struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+
+ ufs_qcom_phy->host_ctrl_rev_major = major;
+ ufs_qcom_phy->host_ctrl_rev_minor = minor;
+ ufs_qcom_phy->host_ctrl_rev_step = step;
+}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_save_controller_version);
+
+static int ufs_qcom_phy_is_pcs_ready(struct ufs_qcom_phy *ufs_qcom_phy)
+{
+ if (!ufs_qcom_phy->phy_spec_ops->is_physical_coding_sublayer_ready) {
+ dev_err(ufs_qcom_phy->dev, "%s: is_physical_coding_sublayer_ready() callback is not supported\n",
+ __func__);
+ return -ENOTSUPP;
+ }
+
+ return ufs_qcom_phy->phy_spec_ops->
+ is_physical_coding_sublayer_ready(ufs_qcom_phy);
+}
+
+int ufs_qcom_phy_power_on(struct phy *generic_phy)
+{
+ struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
+ struct device *dev = phy_common->dev;
+ int err;
+
+ if (phy_common->is_powered_on)
+ return 0;
+
+ if (!phy_common->is_started) {
+ err = ufs_qcom_phy_start_serdes(phy_common);
+ if (err)
+ return err;
+
+ err = ufs_qcom_phy_is_pcs_ready(phy_common);
+ if (err)
+ return err;
+
+ phy_common->is_started = true;
+ }
+
+ err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_phy);
+ if (err) {
+ dev_err(dev, "%s enable vdda_phy failed, err=%d\n",
+ __func__, err);
+ goto out;
+ }
+
+ phy_common->phy_spec_ops->power_control(phy_common, true);
+
+ /* vdda_pll also enables ref clock LDOs so enable it first */
+ err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_pll);
+ if (err) {
+ dev_err(dev, "%s enable vdda_pll failed, err=%d\n",
+ __func__, err);
+ goto out_disable_phy;
+ }
+
+ err = ufs_qcom_phy_enable_iface_clk(phy_common);
+ if (err) {
+ dev_err(dev, "%s enable phy iface clock failed, err=%d\n",
+ __func__, err);
+ goto out_disable_pll;
+ }
+
+ err = ufs_qcom_phy_enable_ref_clk(phy_common);
+ if (err) {
+ dev_err(dev, "%s enable phy ref clock failed, err=%d\n",
+ __func__, err);
+ goto out_disable_iface_clk;
+ }
+
+ /* enable device PHY ref_clk pad rail */
+ if (phy_common->vddp_ref_clk.reg) {
+ err = ufs_qcom_phy_enable_vreg(dev,
+ &phy_common->vddp_ref_clk);
+ if (err) {
+ dev_err(dev, "%s enable vddp_ref_clk failed, err=%d\n",
+ __func__, err);
+ goto out_disable_ref_clk;
+ }
+ }
+
+ phy_common->is_powered_on = true;
+ goto out;
+
+out_disable_ref_clk:
+ ufs_qcom_phy_disable_ref_clk(phy_common);
+out_disable_iface_clk:
+ ufs_qcom_phy_disable_iface_clk(phy_common);
+out_disable_pll:
+ ufs_qcom_phy_disable_vreg(dev, &phy_common->vdda_pll);
+out_disable_phy:
+ ufs_qcom_phy_disable_vreg(dev, &phy_common->vdda_phy);
+out:
+ return err;
+}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_on);
+
+int ufs_qcom_phy_power_off(struct phy *generic_phy)
+{
+ struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
+
+ if (!phy_common->is_powered_on)
+ return 0;
+
+ phy_common->phy_spec_ops->power_control(phy_common, false);
+
+ if (phy_common->vddp_ref_clk.reg)
+ ufs_qcom_phy_disable_vreg(phy_common->dev,
+ &phy_common->vddp_ref_clk);
+ ufs_qcom_phy_disable_ref_clk(phy_common);
+ ufs_qcom_phy_disable_iface_clk(phy_common);
+
+ ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_pll);
+ ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_phy);
+ phy_common->is_powered_on = false;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_off);
+
+MODULE_AUTHOR("Yaniv Gardi <ygardi@codeaurora.org>");
+MODULE_AUTHOR("Vivek Gautam <vivek.gautam@codeaurora.org>");
+MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/qualcomm/phy-qcom-usb-hs.c b/drivers/phy/qualcomm/phy-qcom-usb-hs.c
new file mode 100644
index 000000000..5629d56a6
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-usb-hs.c
@@ -0,0 +1,291 @@
+/**
+ * Copyright (C) 2016 Linaro Ltd
+ *
+ * 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/module.h>
+#include <linux/ulpi/driver.h>
+#include <linux/ulpi/regs.h>
+#include <linux/clk.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/reset.h>
+#include <linux/extcon.h>
+#include <linux/notifier.h>
+
+#define ULPI_PWR_CLK_MNG_REG 0x88
+# define ULPI_PWR_OTG_COMP_DISABLE BIT(0)
+
+#define ULPI_MISC_A 0x96
+# define ULPI_MISC_A_VBUSVLDEXTSEL BIT(1)
+# define ULPI_MISC_A_VBUSVLDEXT BIT(0)
+
+
+struct ulpi_seq {
+ u8 addr;
+ u8 val;
+};
+
+struct qcom_usb_hs_phy {
+ struct ulpi *ulpi;
+ struct phy *phy;
+ struct clk *ref_clk;
+ struct clk *sleep_clk;
+ struct regulator *v1p8;
+ struct regulator *v3p3;
+ struct reset_control *reset;
+ struct ulpi_seq *init_seq;
+ struct extcon_dev *vbus_edev;
+ struct notifier_block vbus_notify;
+};
+
+static int qcom_usb_hs_phy_set_mode(struct phy *phy, enum phy_mode mode)
+{
+ struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy);
+ u8 addr;
+ int ret;
+
+ if (!uphy->vbus_edev) {
+ u8 val = 0;
+
+ switch (mode) {
+ case PHY_MODE_USB_OTG:
+ case PHY_MODE_USB_HOST:
+ val |= ULPI_INT_IDGRD;
+ /* fall through */
+ case PHY_MODE_USB_DEVICE:
+ val |= ULPI_INT_SESS_VALID;
+ default:
+ break;
+ }
+
+ ret = ulpi_write(uphy->ulpi, ULPI_USB_INT_EN_RISE, val);
+ if (ret)
+ return ret;
+ ret = ulpi_write(uphy->ulpi, ULPI_USB_INT_EN_FALL, val);
+ } else {
+ switch (mode) {
+ case PHY_MODE_USB_OTG:
+ case PHY_MODE_USB_DEVICE:
+ addr = ULPI_SET(ULPI_MISC_A);
+ break;
+ case PHY_MODE_USB_HOST:
+ addr = ULPI_CLR(ULPI_MISC_A);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = ulpi_write(uphy->ulpi, ULPI_SET(ULPI_PWR_CLK_MNG_REG),
+ ULPI_PWR_OTG_COMP_DISABLE);
+ if (ret)
+ return ret;
+ ret = ulpi_write(uphy->ulpi, addr, ULPI_MISC_A_VBUSVLDEXTSEL);
+ }
+
+ return ret;
+}
+
+static int
+qcom_usb_hs_phy_vbus_notifier(struct notifier_block *nb, unsigned long event,
+ void *ptr)
+{
+ struct qcom_usb_hs_phy *uphy;
+ u8 addr;
+
+ uphy = container_of(nb, struct qcom_usb_hs_phy, vbus_notify);
+
+ if (event)
+ addr = ULPI_SET(ULPI_MISC_A);
+ else
+ addr = ULPI_CLR(ULPI_MISC_A);
+
+ return ulpi_write(uphy->ulpi, addr, ULPI_MISC_A_VBUSVLDEXT);
+}
+
+static int qcom_usb_hs_phy_power_on(struct phy *phy)
+{
+ struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy);
+ struct ulpi *ulpi = uphy->ulpi;
+ const struct ulpi_seq *seq;
+ int ret, state;
+
+ ret = clk_prepare_enable(uphy->ref_clk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(uphy->sleep_clk);
+ if (ret)
+ goto err_sleep;
+
+ ret = regulator_set_load(uphy->v1p8, 50000);
+ if (ret < 0)
+ goto err_1p8;
+
+ ret = regulator_enable(uphy->v1p8);
+ if (ret)
+ goto err_1p8;
+
+ ret = regulator_set_voltage_triplet(uphy->v3p3, 3050000, 3300000,
+ 3300000);
+ if (ret)
+ goto err_3p3;
+
+ ret = regulator_set_load(uphy->v3p3, 50000);
+ if (ret < 0)
+ goto err_3p3;
+
+ ret = regulator_enable(uphy->v3p3);
+ if (ret)
+ goto err_3p3;
+
+ for (seq = uphy->init_seq; seq->addr; seq++) {
+ ret = ulpi_write(ulpi, ULPI_EXT_VENDOR_SPECIFIC + seq->addr,
+ seq->val);
+ if (ret)
+ goto err_ulpi;
+ }
+
+ if (uphy->reset) {
+ ret = reset_control_reset(uphy->reset);
+ if (ret)
+ goto err_ulpi;
+ }
+
+ if (uphy->vbus_edev) {
+ state = extcon_get_state(uphy->vbus_edev, EXTCON_USB);
+ /* setup initial state */
+ qcom_usb_hs_phy_vbus_notifier(&uphy->vbus_notify, state,
+ uphy->vbus_edev);
+ ret = extcon_register_notifier(uphy->vbus_edev, EXTCON_USB,
+ &uphy->vbus_notify);
+ if (ret)
+ goto err_ulpi;
+ }
+
+ return 0;
+err_ulpi:
+ regulator_disable(uphy->v3p3);
+err_3p3:
+ regulator_disable(uphy->v1p8);
+err_1p8:
+ clk_disable_unprepare(uphy->sleep_clk);
+err_sleep:
+ clk_disable_unprepare(uphy->ref_clk);
+ return ret;
+}
+
+static int qcom_usb_hs_phy_power_off(struct phy *phy)
+{
+ struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy);
+
+ if (uphy->vbus_edev)
+ extcon_unregister_notifier(uphy->vbus_edev, EXTCON_USB,
+ &uphy->vbus_notify);
+ regulator_disable(uphy->v3p3);
+ regulator_disable(uphy->v1p8);
+ clk_disable_unprepare(uphy->sleep_clk);
+ clk_disable_unprepare(uphy->ref_clk);
+
+ return 0;
+}
+
+static const struct phy_ops qcom_usb_hs_phy_ops = {
+ .power_on = qcom_usb_hs_phy_power_on,
+ .power_off = qcom_usb_hs_phy_power_off,
+ .set_mode = qcom_usb_hs_phy_set_mode,
+ .owner = THIS_MODULE,
+};
+
+static int qcom_usb_hs_phy_probe(struct ulpi *ulpi)
+{
+ struct qcom_usb_hs_phy *uphy;
+ struct phy_provider *p;
+ struct clk *clk;
+ struct regulator *reg;
+ struct reset_control *reset;
+ int size;
+ int ret;
+
+ uphy = devm_kzalloc(&ulpi->dev, sizeof(*uphy), GFP_KERNEL);
+ if (!uphy)
+ return -ENOMEM;
+ ulpi_set_drvdata(ulpi, uphy);
+ uphy->ulpi = ulpi;
+
+ size = of_property_count_u8_elems(ulpi->dev.of_node, "qcom,init-seq");
+ if (size < 0)
+ size = 0;
+ uphy->init_seq = devm_kmalloc_array(&ulpi->dev, (size / 2) + 1,
+ sizeof(*uphy->init_seq), GFP_KERNEL);
+ if (!uphy->init_seq)
+ return -ENOMEM;
+ ret = of_property_read_u8_array(ulpi->dev.of_node, "qcom,init-seq",
+ (u8 *)uphy->init_seq, size);
+ if (ret && size)
+ return ret;
+ /* NUL terminate */
+ uphy->init_seq[size / 2].addr = uphy->init_seq[size / 2].val = 0;
+
+ uphy->ref_clk = clk = devm_clk_get(&ulpi->dev, "ref");
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ uphy->sleep_clk = clk = devm_clk_get(&ulpi->dev, "sleep");
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ uphy->v1p8 = reg = devm_regulator_get(&ulpi->dev, "v1p8");
+ if (IS_ERR(reg))
+ return PTR_ERR(reg);
+
+ uphy->v3p3 = reg = devm_regulator_get(&ulpi->dev, "v3p3");
+ if (IS_ERR(reg))
+ return PTR_ERR(reg);
+
+ uphy->reset = reset = devm_reset_control_get(&ulpi->dev, "por");
+ if (IS_ERR(reset)) {
+ if (PTR_ERR(reset) == -EPROBE_DEFER)
+ return PTR_ERR(reset);
+ uphy->reset = NULL;
+ }
+
+ uphy->phy = devm_phy_create(&ulpi->dev, ulpi->dev.of_node,
+ &qcom_usb_hs_phy_ops);
+ if (IS_ERR(uphy->phy))
+ return PTR_ERR(uphy->phy);
+
+ uphy->vbus_edev = extcon_get_edev_by_phandle(&ulpi->dev, 0);
+ if (IS_ERR(uphy->vbus_edev)) {
+ if (PTR_ERR(uphy->vbus_edev) != -ENODEV)
+ return PTR_ERR(uphy->vbus_edev);
+ uphy->vbus_edev = NULL;
+ }
+
+ uphy->vbus_notify.notifier_call = qcom_usb_hs_phy_vbus_notifier;
+ phy_set_drvdata(uphy->phy, uphy);
+
+ p = devm_of_phy_provider_register(&ulpi->dev, of_phy_simple_xlate);
+ return PTR_ERR_OR_ZERO(p);
+}
+
+static const struct of_device_id qcom_usb_hs_phy_match[] = {
+ { .compatible = "qcom,usb-hs-phy", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, qcom_usb_hs_phy_match);
+
+static struct ulpi_driver qcom_usb_hs_phy_driver = {
+ .probe = qcom_usb_hs_phy_probe,
+ .driver = {
+ .name = "qcom_usb_hs_phy",
+ .of_match_table = qcom_usb_hs_phy_match,
+ },
+};
+module_ulpi_driver(qcom_usb_hs_phy_driver);
+
+MODULE_DESCRIPTION("Qualcomm USB HS phy");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/qualcomm/phy-qcom-usb-hsic.c b/drivers/phy/qualcomm/phy-qcom-usb-hsic.c
new file mode 100644
index 000000000..c110563a7
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-usb-hsic.c
@@ -0,0 +1,159 @@
+/**
+ * Copyright (C) 2016 Linaro Ltd
+ *
+ * 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/module.h>
+#include <linux/ulpi/driver.h>
+#include <linux/ulpi/regs.h>
+#include <linux/phy/phy.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/pinctrl/pinctrl-state.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+
+#define ULPI_HSIC_CFG 0x30
+#define ULPI_HSIC_IO_CAL 0x33
+
+struct qcom_usb_hsic_phy {
+ struct ulpi *ulpi;
+ struct phy *phy;
+ struct pinctrl *pctl;
+ struct clk *phy_clk;
+ struct clk *cal_clk;
+ struct clk *cal_sleep_clk;
+};
+
+static int qcom_usb_hsic_phy_power_on(struct phy *phy)
+{
+ struct qcom_usb_hsic_phy *uphy = phy_get_drvdata(phy);
+ struct ulpi *ulpi = uphy->ulpi;
+ struct pinctrl_state *pins_default;
+ int ret;
+
+ ret = clk_prepare_enable(uphy->phy_clk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(uphy->cal_clk);
+ if (ret)
+ goto err_cal;
+
+ ret = clk_prepare_enable(uphy->cal_sleep_clk);
+ if (ret)
+ goto err_sleep;
+
+ /* Set periodic calibration interval to ~2.048sec in HSIC_IO_CAL_REG */
+ ret = ulpi_write(ulpi, ULPI_HSIC_IO_CAL, 0xff);
+ if (ret)
+ goto err_ulpi;
+
+ /* Enable periodic IO calibration in HSIC_CFG register */
+ ret = ulpi_write(ulpi, ULPI_HSIC_CFG, 0xa8);
+ if (ret)
+ goto err_ulpi;
+
+ /* Configure pins for HSIC functionality */
+ pins_default = pinctrl_lookup_state(uphy->pctl, PINCTRL_STATE_DEFAULT);
+ if (IS_ERR(pins_default))
+ return PTR_ERR(pins_default);
+
+ ret = pinctrl_select_state(uphy->pctl, pins_default);
+ if (ret)
+ goto err_ulpi;
+
+ /* Enable HSIC mode in HSIC_CFG register */
+ ret = ulpi_write(ulpi, ULPI_SET(ULPI_HSIC_CFG), 0x01);
+ if (ret)
+ goto err_ulpi;
+
+ /* Disable auto-resume */
+ ret = ulpi_write(ulpi, ULPI_CLR(ULPI_IFC_CTRL),
+ ULPI_IFC_CTRL_AUTORESUME);
+ if (ret)
+ goto err_ulpi;
+
+ return ret;
+err_ulpi:
+ clk_disable_unprepare(uphy->cal_sleep_clk);
+err_sleep:
+ clk_disable_unprepare(uphy->cal_clk);
+err_cal:
+ clk_disable_unprepare(uphy->phy_clk);
+ return ret;
+}
+
+static int qcom_usb_hsic_phy_power_off(struct phy *phy)
+{
+ struct qcom_usb_hsic_phy *uphy = phy_get_drvdata(phy);
+
+ clk_disable_unprepare(uphy->cal_sleep_clk);
+ clk_disable_unprepare(uphy->cal_clk);
+ clk_disable_unprepare(uphy->phy_clk);
+
+ return 0;
+}
+
+static const struct phy_ops qcom_usb_hsic_phy_ops = {
+ .power_on = qcom_usb_hsic_phy_power_on,
+ .power_off = qcom_usb_hsic_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int qcom_usb_hsic_phy_probe(struct ulpi *ulpi)
+{
+ struct qcom_usb_hsic_phy *uphy;
+ struct phy_provider *p;
+ struct clk *clk;
+
+ uphy = devm_kzalloc(&ulpi->dev, sizeof(*uphy), GFP_KERNEL);
+ if (!uphy)
+ return -ENOMEM;
+ ulpi_set_drvdata(ulpi, uphy);
+
+ uphy->ulpi = ulpi;
+ uphy->pctl = devm_pinctrl_get(&ulpi->dev);
+ if (IS_ERR(uphy->pctl))
+ return PTR_ERR(uphy->pctl);
+
+ uphy->phy_clk = clk = devm_clk_get(&ulpi->dev, "phy");
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ uphy->cal_clk = clk = devm_clk_get(&ulpi->dev, "cal");
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ uphy->cal_sleep_clk = clk = devm_clk_get(&ulpi->dev, "cal_sleep");
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ uphy->phy = devm_phy_create(&ulpi->dev, ulpi->dev.of_node,
+ &qcom_usb_hsic_phy_ops);
+ if (IS_ERR(uphy->phy))
+ return PTR_ERR(uphy->phy);
+ phy_set_drvdata(uphy->phy, uphy);
+
+ p = devm_of_phy_provider_register(&ulpi->dev, of_phy_simple_xlate);
+ return PTR_ERR_OR_ZERO(p);
+}
+
+static const struct of_device_id qcom_usb_hsic_phy_match[] = {
+ { .compatible = "qcom,usb-hsic-phy", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, qcom_usb_hsic_phy_match);
+
+static struct ulpi_driver qcom_usb_hsic_phy_driver = {
+ .probe = qcom_usb_hsic_phy_probe,
+ .driver = {
+ .name = "qcom_usb_hsic_phy",
+ .of_match_table = qcom_usb_hsic_phy_match,
+ },
+};
+module_ulpi_driver(qcom_usb_hsic_phy_driver);
+
+MODULE_DESCRIPTION("Qualcomm USB HSIC phy");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/ralink/Kconfig b/drivers/phy/ralink/Kconfig
new file mode 100644
index 000000000..14fd21953
--- /dev/null
+++ b/drivers/phy/ralink/Kconfig
@@ -0,0 +1,12 @@
+#
+# PHY drivers for Ralink platforms.
+#
+config PHY_RALINK_USB
+ tristate "Ralink USB PHY driver"
+ depends on RALINK || COMPILE_TEST
+ depends on HAS_IOMEM
+ select GENERIC_PHY
+ select MFD_SYSCON
+ help
+ This option enables support for the Ralink USB PHY found inside
+ RT3352, MT7620, MT7628 and MT7688.
diff --git a/drivers/phy/ralink/Makefile b/drivers/phy/ralink/Makefile
new file mode 100644
index 000000000..5c9e326e8
--- /dev/null
+++ b/drivers/phy/ralink/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_PHY_RALINK_USB) += phy-ralink-usb.o
diff --git a/drivers/phy/ralink/phy-ralink-usb.c b/drivers/phy/ralink/phy-ralink-usb.c
new file mode 100644
index 000000000..4fea31f8a
--- /dev/null
+++ b/drivers/phy/ralink/phy-ralink-usb.c
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2017 John Crispin <john@phrozen.org>
+ *
+ * Based on code from
+ * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
+ *
+ * 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/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_platform.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#define RT_SYSC_REG_SYSCFG1 0x014
+#define RT_SYSC_REG_CLKCFG1 0x030
+#define RT_SYSC_REG_USB_PHY_CFG 0x05c
+
+#define OFS_U2_PHY_AC0 0x800
+#define OFS_U2_PHY_AC1 0x804
+#define OFS_U2_PHY_AC2 0x808
+#define OFS_U2_PHY_ACR0 0x810
+#define OFS_U2_PHY_ACR1 0x814
+#define OFS_U2_PHY_ACR2 0x818
+#define OFS_U2_PHY_ACR3 0x81C
+#define OFS_U2_PHY_ACR4 0x820
+#define OFS_U2_PHY_AMON0 0x824
+#define OFS_U2_PHY_DCR0 0x860
+#define OFS_U2_PHY_DCR1 0x864
+#define OFS_U2_PHY_DTM0 0x868
+#define OFS_U2_PHY_DTM1 0x86C
+
+#define RT_RSTCTRL_UDEV BIT(25)
+#define RT_RSTCTRL_UHST BIT(22)
+#define RT_SYSCFG1_USB0_HOST_MODE BIT(10)
+
+#define MT7620_CLKCFG1_UPHY0_CLK_EN BIT(25)
+#define MT7620_CLKCFG1_UPHY1_CLK_EN BIT(22)
+#define RT_CLKCFG1_UPHY1_CLK_EN BIT(20)
+#define RT_CLKCFG1_UPHY0_CLK_EN BIT(18)
+
+#define USB_PHY_UTMI_8B60M BIT(1)
+#define UDEV_WAKEUP BIT(0)
+
+struct ralink_usb_phy {
+ struct reset_control *rstdev;
+ struct reset_control *rsthost;
+ u32 clk;
+ struct phy *phy;
+ void __iomem *base;
+ struct regmap *sysctl;
+};
+
+static void u2_phy_w32(struct ralink_usb_phy *phy, u32 val, u32 reg)
+{
+ writel(val, phy->base + reg);
+}
+
+static u32 u2_phy_r32(struct ralink_usb_phy *phy, u32 reg)
+{
+ return readl(phy->base + reg);
+}
+
+static void ralink_usb_phy_init(struct ralink_usb_phy *phy)
+{
+ u2_phy_r32(phy, OFS_U2_PHY_AC2);
+ u2_phy_r32(phy, OFS_U2_PHY_ACR0);
+ u2_phy_r32(phy, OFS_U2_PHY_DCR0);
+
+ u2_phy_w32(phy, 0x00ffff02, OFS_U2_PHY_DCR0);
+ u2_phy_r32(phy, OFS_U2_PHY_DCR0);
+ u2_phy_w32(phy, 0x00555502, OFS_U2_PHY_DCR0);
+ u2_phy_r32(phy, OFS_U2_PHY_DCR0);
+ u2_phy_w32(phy, 0x00aaaa02, OFS_U2_PHY_DCR0);
+ u2_phy_r32(phy, OFS_U2_PHY_DCR0);
+ u2_phy_w32(phy, 0x00000402, OFS_U2_PHY_DCR0);
+ u2_phy_r32(phy, OFS_U2_PHY_DCR0);
+ u2_phy_w32(phy, 0x0048086a, OFS_U2_PHY_AC0);
+ u2_phy_w32(phy, 0x4400001c, OFS_U2_PHY_AC1);
+ u2_phy_w32(phy, 0xc0200000, OFS_U2_PHY_ACR3);
+ u2_phy_w32(phy, 0x02000000, OFS_U2_PHY_DTM0);
+}
+
+static int ralink_usb_phy_power_on(struct phy *_phy)
+{
+ struct ralink_usb_phy *phy = phy_get_drvdata(_phy);
+ u32 t;
+
+ /* enable the phy */
+ regmap_update_bits(phy->sysctl, RT_SYSC_REG_CLKCFG1,
+ phy->clk, phy->clk);
+
+ /* setup host mode */
+ regmap_update_bits(phy->sysctl, RT_SYSC_REG_SYSCFG1,
+ RT_SYSCFG1_USB0_HOST_MODE,
+ RT_SYSCFG1_USB0_HOST_MODE);
+
+ /* deassert the reset lines */
+ reset_control_deassert(phy->rsthost);
+ reset_control_deassert(phy->rstdev);
+
+ /*
+ * The SDK kernel had a delay of 100ms. however on device
+ * testing showed that 10ms is enough
+ */
+ mdelay(10);
+
+ if (phy->base)
+ ralink_usb_phy_init(phy);
+
+ /* print some status info */
+ regmap_read(phy->sysctl, RT_SYSC_REG_USB_PHY_CFG, &t);
+ dev_info(&phy->phy->dev, "remote usb device wakeup %s\n",
+ (t & UDEV_WAKEUP) ? ("enabled") : ("disabled"));
+ if (t & USB_PHY_UTMI_8B60M)
+ dev_info(&phy->phy->dev, "UTMI 8bit 60MHz\n");
+ else
+ dev_info(&phy->phy->dev, "UTMI 16bit 30MHz\n");
+
+ return 0;
+}
+
+static int ralink_usb_phy_power_off(struct phy *_phy)
+{
+ struct ralink_usb_phy *phy = phy_get_drvdata(_phy);
+
+ /* disable the phy */
+ regmap_update_bits(phy->sysctl, RT_SYSC_REG_CLKCFG1,
+ phy->clk, 0);
+
+ /* assert the reset lines */
+ reset_control_assert(phy->rstdev);
+ reset_control_assert(phy->rsthost);
+
+ return 0;
+}
+
+static struct phy_ops ralink_usb_phy_ops = {
+ .power_on = ralink_usb_phy_power_on,
+ .power_off = ralink_usb_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static const struct of_device_id ralink_usb_phy_of_match[] = {
+ {
+ .compatible = "ralink,rt3352-usbphy",
+ .data = (void *)(uintptr_t)(RT_CLKCFG1_UPHY1_CLK_EN |
+ RT_CLKCFG1_UPHY0_CLK_EN)
+ },
+ {
+ .compatible = "mediatek,mt7620-usbphy",
+ .data = (void *)(uintptr_t)(MT7620_CLKCFG1_UPHY1_CLK_EN |
+ MT7620_CLKCFG1_UPHY0_CLK_EN)
+ },
+ {
+ .compatible = "mediatek,mt7628-usbphy",
+ .data = (void *)(uintptr_t)(MT7620_CLKCFG1_UPHY1_CLK_EN |
+ MT7620_CLKCFG1_UPHY0_CLK_EN) },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ralink_usb_phy_of_match);
+
+static int ralink_usb_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct phy_provider *phy_provider;
+ const struct of_device_id *match;
+ struct ralink_usb_phy *phy;
+
+ match = of_match_device(ralink_usb_phy_of_match, &pdev->dev);
+ if (!match)
+ return -ENODEV;
+
+ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ phy->clk = (uintptr_t)match->data;
+ phy->base = NULL;
+
+ phy->sysctl = syscon_regmap_lookup_by_phandle(dev->of_node, "ralink,sysctl");
+ if (IS_ERR(phy->sysctl)) {
+ dev_err(dev, "failed to get sysctl registers\n");
+ return PTR_ERR(phy->sysctl);
+ }
+
+ /* The MT7628 and MT7688 require extra setup of PHY registers. */
+ if (of_device_is_compatible(dev->of_node, "mediatek,mt7628-usbphy")) {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ phy->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(phy->base)) {
+ dev_err(dev, "failed to remap register memory\n");
+ return PTR_ERR(phy->base);
+ }
+ }
+
+ phy->rsthost = devm_reset_control_get(&pdev->dev, "host");
+ if (IS_ERR(phy->rsthost)) {
+ dev_err(dev, "host reset is missing\n");
+ return PTR_ERR(phy->rsthost);
+ }
+
+ phy->rstdev = devm_reset_control_get(&pdev->dev, "device");
+ if (IS_ERR(phy->rstdev)) {
+ dev_err(dev, "device reset is missing\n");
+ return PTR_ERR(phy->rstdev);
+ }
+
+ phy->phy = devm_phy_create(dev, NULL, &ralink_usb_phy_ops);
+ if (IS_ERR(phy->phy)) {
+ dev_err(dev, "failed to create PHY\n");
+ return PTR_ERR(phy->phy);
+ }
+ phy_set_drvdata(phy->phy, phy);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static struct platform_driver ralink_usb_phy_driver = {
+ .probe = ralink_usb_phy_probe,
+ .driver = {
+ .of_match_table = ralink_usb_phy_of_match,
+ .name = "ralink-usb-phy",
+ }
+};
+module_platform_driver(ralink_usb_phy_driver);
+
+MODULE_DESCRIPTION("Ralink USB phy driver");
+MODULE_AUTHOR("John Crispin <john@phrozen.org>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/renesas/Kconfig b/drivers/phy/renesas/Kconfig
new file mode 100644
index 000000000..4bd390c79
--- /dev/null
+++ b/drivers/phy/renesas/Kconfig
@@ -0,0 +1,33 @@
+#
+# Phy drivers for Renesas platforms
+#
+config PHY_RCAR_GEN2
+ tristate "Renesas R-Car generation 2 USB PHY driver"
+ depends on ARCH_RENESAS
+ depends on GENERIC_PHY
+ help
+ Support for USB PHY found on Renesas R-Car generation 2 SoCs.
+
+config PHY_RCAR_GEN3_PCIE
+ tristate "Renesas R-Car generation 3 PCIe PHY driver"
+ depends on ARCH_RENESAS
+ select GENERIC_PHY
+ help
+ Support for the PCIe PHY found on Renesas R-Car generation 3 SoCs.
+
+config PHY_RCAR_GEN3_USB2
+ tristate "Renesas R-Car generation 3 USB 2.0 PHY driver"
+ depends on ARCH_RENESAS
+ depends on EXTCON
+ depends on USB_SUPPORT
+ select GENERIC_PHY
+ select USB_COMMON
+ help
+ Support for USB 2.0 PHY found on Renesas R-Car generation 3 SoCs.
+
+config PHY_RCAR_GEN3_USB3
+ tristate "Renesas R-Car generation 3 USB 3.0 PHY driver"
+ depends on ARCH_RENESAS || COMPILE_TEST
+ select GENERIC_PHY
+ help
+ Support for USB 3.0 PHY found on Renesas R-Car generation 3 SoCs.
diff --git a/drivers/phy/renesas/Makefile b/drivers/phy/renesas/Makefile
new file mode 100644
index 000000000..4b76fc439
--- /dev/null
+++ b/drivers/phy/renesas/Makefile
@@ -0,0 +1,4 @@
+obj-$(CONFIG_PHY_RCAR_GEN2) += phy-rcar-gen2.o
+obj-$(CONFIG_PHY_RCAR_GEN3_PCIE) += phy-rcar-gen3-pcie.o
+obj-$(CONFIG_PHY_RCAR_GEN3_USB2) += phy-rcar-gen3-usb2.o
+obj-$(CONFIG_PHY_RCAR_GEN3_USB3) += phy-rcar-gen3-usb3.o
diff --git a/drivers/phy/renesas/phy-rcar-gen2.c b/drivers/phy/renesas/phy-rcar-gen2.c
new file mode 100644
index 000000000..aa02b19b7
--- /dev/null
+++ b/drivers/phy/renesas/phy-rcar-gen2.c
@@ -0,0 +1,339 @@
+/*
+ * Renesas R-Car Gen2 PHY driver
+ *
+ * Copyright (C) 2014 Renesas Solutions Corp.
+ * Copyright (C) 2014 Cogent Embedded, Inc.
+ *
+ * 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/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/atomic.h>
+
+#define USBHS_LPSTS 0x02
+#define USBHS_UGCTRL 0x80
+#define USBHS_UGCTRL2 0x84
+#define USBHS_UGSTS 0x88 /* From technical update */
+
+/* Low Power Status register (LPSTS) */
+#define USBHS_LPSTS_SUSPM 0x4000
+
+/* USB General control register (UGCTRL) */
+#define USBHS_UGCTRL_CONNECT 0x00000004
+#define USBHS_UGCTRL_PLLRESET 0x00000001
+
+/* USB General control register 2 (UGCTRL2) */
+#define USBHS_UGCTRL2_USB2SEL 0x80000000
+#define USBHS_UGCTRL2_USB2SEL_PCI 0x00000000
+#define USBHS_UGCTRL2_USB2SEL_USB30 0x80000000
+#define USBHS_UGCTRL2_USB0SEL 0x00000030
+#define USBHS_UGCTRL2_USB0SEL_PCI 0x00000010
+#define USBHS_UGCTRL2_USB0SEL_HS_USB 0x00000030
+
+/* USB General status register (UGSTS) */
+#define USBHS_UGSTS_LOCK 0x00000100 /* From technical update */
+
+#define PHYS_PER_CHANNEL 2
+
+struct rcar_gen2_phy {
+ struct phy *phy;
+ struct rcar_gen2_channel *channel;
+ int number;
+ u32 select_value;
+};
+
+struct rcar_gen2_channel {
+ struct device_node *of_node;
+ struct rcar_gen2_phy_driver *drv;
+ struct rcar_gen2_phy phys[PHYS_PER_CHANNEL];
+ int selected_phy;
+ u32 select_mask;
+};
+
+struct rcar_gen2_phy_driver {
+ void __iomem *base;
+ struct clk *clk;
+ spinlock_t lock;
+ int num_channels;
+ struct rcar_gen2_channel *channels;
+};
+
+static int rcar_gen2_phy_init(struct phy *p)
+{
+ struct rcar_gen2_phy *phy = phy_get_drvdata(p);
+ struct rcar_gen2_channel *channel = phy->channel;
+ struct rcar_gen2_phy_driver *drv = channel->drv;
+ unsigned long flags;
+ u32 ugctrl2;
+
+ /*
+ * Try to acquire exclusive access to PHY. The first driver calling
+ * phy_init() on a given channel wins, and all attempts to use another
+ * PHY on this channel will fail until phy_exit() is called by the first
+ * driver. Achieving this with cmpxcgh() should be SMP-safe.
+ */
+ if (cmpxchg(&channel->selected_phy, -1, phy->number) != -1)
+ return -EBUSY;
+
+ clk_prepare_enable(drv->clk);
+
+ spin_lock_irqsave(&drv->lock, flags);
+ ugctrl2 = readl(drv->base + USBHS_UGCTRL2);
+ ugctrl2 &= ~channel->select_mask;
+ ugctrl2 |= phy->select_value;
+ writel(ugctrl2, drv->base + USBHS_UGCTRL2);
+ spin_unlock_irqrestore(&drv->lock, flags);
+ return 0;
+}
+
+static int rcar_gen2_phy_exit(struct phy *p)
+{
+ struct rcar_gen2_phy *phy = phy_get_drvdata(p);
+ struct rcar_gen2_channel *channel = phy->channel;
+
+ clk_disable_unprepare(channel->drv->clk);
+
+ channel->selected_phy = -1;
+
+ return 0;
+}
+
+static int rcar_gen2_phy_power_on(struct phy *p)
+{
+ struct rcar_gen2_phy *phy = phy_get_drvdata(p);
+ struct rcar_gen2_phy_driver *drv = phy->channel->drv;
+ void __iomem *base = drv->base;
+ unsigned long flags;
+ u32 value;
+ int err = 0, i;
+
+ /* Skip if it's not USBHS */
+ if (phy->select_value != USBHS_UGCTRL2_USB0SEL_HS_USB)
+ return 0;
+
+ spin_lock_irqsave(&drv->lock, flags);
+
+ /* Power on USBHS PHY */
+ value = readl(base + USBHS_UGCTRL);
+ value &= ~USBHS_UGCTRL_PLLRESET;
+ writel(value, base + USBHS_UGCTRL);
+
+ value = readw(base + USBHS_LPSTS);
+ value |= USBHS_LPSTS_SUSPM;
+ writew(value, base + USBHS_LPSTS);
+
+ for (i = 0; i < 20; i++) {
+ value = readl(base + USBHS_UGSTS);
+ if ((value & USBHS_UGSTS_LOCK) == USBHS_UGSTS_LOCK) {
+ value = readl(base + USBHS_UGCTRL);
+ value |= USBHS_UGCTRL_CONNECT;
+ writel(value, base + USBHS_UGCTRL);
+ goto out;
+ }
+ udelay(1);
+ }
+
+ /* Timed out waiting for the PLL lock */
+ err = -ETIMEDOUT;
+
+out:
+ spin_unlock_irqrestore(&drv->lock, flags);
+
+ return err;
+}
+
+static int rcar_gen2_phy_power_off(struct phy *p)
+{
+ struct rcar_gen2_phy *phy = phy_get_drvdata(p);
+ struct rcar_gen2_phy_driver *drv = phy->channel->drv;
+ void __iomem *base = drv->base;
+ unsigned long flags;
+ u32 value;
+
+ /* Skip if it's not USBHS */
+ if (phy->select_value != USBHS_UGCTRL2_USB0SEL_HS_USB)
+ return 0;
+
+ spin_lock_irqsave(&drv->lock, flags);
+
+ /* Power off USBHS PHY */
+ value = readl(base + USBHS_UGCTRL);
+ value &= ~USBHS_UGCTRL_CONNECT;
+ writel(value, base + USBHS_UGCTRL);
+
+ value = readw(base + USBHS_LPSTS);
+ value &= ~USBHS_LPSTS_SUSPM;
+ writew(value, base + USBHS_LPSTS);
+
+ value = readl(base + USBHS_UGCTRL);
+ value |= USBHS_UGCTRL_PLLRESET;
+ writel(value, base + USBHS_UGCTRL);
+
+ spin_unlock_irqrestore(&drv->lock, flags);
+
+ return 0;
+}
+
+static const struct phy_ops rcar_gen2_phy_ops = {
+ .init = rcar_gen2_phy_init,
+ .exit = rcar_gen2_phy_exit,
+ .power_on = rcar_gen2_phy_power_on,
+ .power_off = rcar_gen2_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static const struct of_device_id rcar_gen2_phy_match_table[] = {
+ { .compatible = "renesas,usb-phy-r8a7790" },
+ { .compatible = "renesas,usb-phy-r8a7791" },
+ { .compatible = "renesas,usb-phy-r8a7794" },
+ { .compatible = "renesas,rcar-gen2-usb-phy" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rcar_gen2_phy_match_table);
+
+static struct phy *rcar_gen2_phy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct rcar_gen2_phy_driver *drv;
+ struct device_node *np = args->np;
+ int i;
+
+ drv = dev_get_drvdata(dev);
+ if (!drv)
+ return ERR_PTR(-EINVAL);
+
+ for (i = 0; i < drv->num_channels; i++) {
+ if (np == drv->channels[i].of_node)
+ break;
+ }
+
+ if (i >= drv->num_channels || args->args[0] >= 2)
+ return ERR_PTR(-ENODEV);
+
+ return drv->channels[i].phys[args->args[0]].phy;
+}
+
+static const u32 select_mask[] = {
+ [0] = USBHS_UGCTRL2_USB0SEL,
+ [2] = USBHS_UGCTRL2_USB2SEL,
+};
+
+static const u32 select_value[][PHYS_PER_CHANNEL] = {
+ [0] = { USBHS_UGCTRL2_USB0SEL_PCI, USBHS_UGCTRL2_USB0SEL_HS_USB },
+ [2] = { USBHS_UGCTRL2_USB2SEL_PCI, USBHS_UGCTRL2_USB2SEL_USB30 },
+};
+
+static int rcar_gen2_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rcar_gen2_phy_driver *drv;
+ struct phy_provider *provider;
+ struct device_node *np;
+ struct resource *res;
+ void __iomem *base;
+ struct clk *clk;
+ int i = 0;
+
+ if (!dev->of_node) {
+ dev_err(dev,
+ "This driver is required to be instantiated from device tree\n");
+ return -EINVAL;
+ }
+
+ clk = devm_clk_get(dev, "usbhs");
+ if (IS_ERR(clk)) {
+ dev_err(dev, "Can't get USBHS clock\n");
+ return PTR_ERR(clk);
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
+ if (!drv)
+ return -ENOMEM;
+
+ spin_lock_init(&drv->lock);
+
+ drv->clk = clk;
+ drv->base = base;
+
+ drv->num_channels = of_get_child_count(dev->of_node);
+ drv->channels = devm_kcalloc(dev, drv->num_channels,
+ sizeof(struct rcar_gen2_channel),
+ GFP_KERNEL);
+ if (!drv->channels)
+ return -ENOMEM;
+
+ for_each_child_of_node(dev->of_node, np) {
+ struct rcar_gen2_channel *channel = drv->channels + i;
+ u32 channel_num;
+ int error, n;
+
+ channel->of_node = np;
+ channel->drv = drv;
+ channel->selected_phy = -1;
+
+ error = of_property_read_u32(np, "reg", &channel_num);
+ if (error || channel_num > 2) {
+ dev_err(dev, "Invalid \"reg\" property\n");
+ of_node_put(np);
+ return error;
+ }
+ channel->select_mask = select_mask[channel_num];
+
+ for (n = 0; n < PHYS_PER_CHANNEL; n++) {
+ struct rcar_gen2_phy *phy = &channel->phys[n];
+
+ phy->channel = channel;
+ phy->number = n;
+ phy->select_value = select_value[channel_num][n];
+
+ phy->phy = devm_phy_create(dev, NULL,
+ &rcar_gen2_phy_ops);
+ if (IS_ERR(phy->phy)) {
+ dev_err(dev, "Failed to create PHY\n");
+ of_node_put(np);
+ return PTR_ERR(phy->phy);
+ }
+ phy_set_drvdata(phy->phy, phy);
+ }
+
+ i++;
+ }
+
+ provider = devm_of_phy_provider_register(dev, rcar_gen2_phy_xlate);
+ if (IS_ERR(provider)) {
+ dev_err(dev, "Failed to register PHY provider\n");
+ return PTR_ERR(provider);
+ }
+
+ dev_set_drvdata(dev, drv);
+
+ return 0;
+}
+
+static struct platform_driver rcar_gen2_phy_driver = {
+ .driver = {
+ .name = "phy_rcar_gen2",
+ .of_match_table = rcar_gen2_phy_match_table,
+ },
+ .probe = rcar_gen2_phy_probe,
+};
+
+module_platform_driver(rcar_gen2_phy_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Renesas R-Car Gen2 PHY");
+MODULE_AUTHOR("Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>");
diff --git a/drivers/phy/renesas/phy-rcar-gen3-pcie.c b/drivers/phy/renesas/phy-rcar-gen3-pcie.c
new file mode 100644
index 000000000..c4e4aa216
--- /dev/null
+++ b/drivers/phy/renesas/phy-rcar-gen3-pcie.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas R-Car Gen3 PCIe PHY driver
+ *
+ * Copyright (C) 2018 Cogent Embedded, Inc.
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define PHY_CTRL 0x4000 /* R8A77980 only */
+
+/* PHY control register (PHY_CTRL) */
+#define PHY_CTRL_PHY_PWDN BIT(2)
+
+struct rcar_gen3_phy {
+ struct phy *phy;
+ spinlock_t lock;
+ void __iomem *base;
+};
+
+static void rcar_gen3_phy_pcie_modify_reg(struct phy *p, unsigned int reg,
+ u32 clear, u32 set)
+{
+ struct rcar_gen3_phy *phy = phy_get_drvdata(p);
+ void __iomem *base = phy->base;
+ unsigned long flags;
+ u32 value;
+
+ spin_lock_irqsave(&phy->lock, flags);
+
+ value = readl(base + reg);
+ value &= ~clear;
+ value |= set;
+ writel(value, base + reg);
+
+ spin_unlock_irqrestore(&phy->lock, flags);
+}
+
+static int r8a77980_phy_pcie_power_on(struct phy *p)
+{
+ /* Power on the PCIe PHY */
+ rcar_gen3_phy_pcie_modify_reg(p, PHY_CTRL, PHY_CTRL_PHY_PWDN, 0);
+
+ return 0;
+}
+
+static int r8a77980_phy_pcie_power_off(struct phy *p)
+{
+ /* Power off the PCIe PHY */
+ rcar_gen3_phy_pcie_modify_reg(p, PHY_CTRL, 0, PHY_CTRL_PHY_PWDN);
+
+ return 0;
+}
+
+static const struct phy_ops r8a77980_phy_pcie_ops = {
+ .power_on = r8a77980_phy_pcie_power_on,
+ .power_off = r8a77980_phy_pcie_power_off,
+ .owner = THIS_MODULE,
+};
+
+static const struct of_device_id rcar_gen3_phy_pcie_match_table[] = {
+ { .compatible = "renesas,r8a77980-pcie-phy" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rcar_gen3_phy_pcie_match_table);
+
+static int rcar_gen3_phy_pcie_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct phy_provider *provider;
+ struct rcar_gen3_phy *phy;
+ struct resource *res;
+ void __iomem *base;
+ int error;
+
+ if (!dev->of_node) {
+ dev_err(dev,
+ "This driver must only be instantiated from the device tree\n");
+ return -EINVAL;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ spin_lock_init(&phy->lock);
+
+ phy->base = base;
+
+ /*
+ * devm_phy_create() will call pm_runtime_enable(&phy->dev);
+ * And then, phy-core will manage runtime PM for this device.
+ */
+ pm_runtime_enable(dev);
+
+ phy->phy = devm_phy_create(dev, NULL, &r8a77980_phy_pcie_ops);
+ if (IS_ERR(phy->phy)) {
+ dev_err(dev, "Failed to create PCIe PHY\n");
+ error = PTR_ERR(phy->phy);
+ goto error;
+ }
+ phy_set_drvdata(phy->phy, phy);
+
+ provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(provider)) {
+ dev_err(dev, "Failed to register PHY provider\n");
+ error = PTR_ERR(provider);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ pm_runtime_disable(dev);
+
+ return error;
+}
+
+static int rcar_gen3_phy_pcie_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+};
+
+static struct platform_driver rcar_gen3_phy_driver = {
+ .driver = {
+ .name = "phy_rcar_gen3_pcie",
+ .of_match_table = rcar_gen3_phy_pcie_match_table,
+ },
+ .probe = rcar_gen3_phy_pcie_probe,
+ .remove = rcar_gen3_phy_pcie_remove,
+};
+
+module_platform_driver(rcar_gen3_phy_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Renesas R-Car Gen3 PCIe PHY");
+MODULE_AUTHOR("Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>");
diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c
new file mode 100644
index 000000000..50cdf2032
--- /dev/null
+++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c
@@ -0,0 +1,536 @@
+/*
+ * Renesas R-Car Gen3 for USB2.0 PHY driver
+ *
+ * Copyright (C) 2015-2017 Renesas Electronics Corporation
+ *
+ * This is based on the phy-rcar-gen2 driver:
+ * Copyright (C) 2014 Renesas Solutions Corp.
+ * Copyright (C) 2014 Cogent Embedded, Inc.
+ *
+ * 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/extcon-provider.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/string.h>
+#include <linux/usb/of.h>
+#include <linux/workqueue.h>
+
+/******* USB2.0 Host registers (original offset is +0x200) *******/
+#define USB2_INT_ENABLE 0x000
+#define USB2_USBCTR 0x00c
+#define USB2_SPD_RSM_TIMSET 0x10c
+#define USB2_OC_TIMSET 0x110
+#define USB2_COMMCTRL 0x600
+#define USB2_OBINTSTA 0x604
+#define USB2_OBINTEN 0x608
+#define USB2_VBCTRL 0x60c
+#define USB2_LINECTRL1 0x610
+#define USB2_ADPCTRL 0x630
+
+/* INT_ENABLE */
+#define USB2_INT_ENABLE_UCOM_INTEN BIT(3)
+#define USB2_INT_ENABLE_USBH_INTB_EN BIT(2)
+#define USB2_INT_ENABLE_USBH_INTA_EN BIT(1)
+#define USB2_INT_ENABLE_INIT (USB2_INT_ENABLE_UCOM_INTEN | \
+ USB2_INT_ENABLE_USBH_INTB_EN | \
+ USB2_INT_ENABLE_USBH_INTA_EN)
+
+/* USBCTR */
+#define USB2_USBCTR_DIRPD BIT(2)
+#define USB2_USBCTR_PLL_RST BIT(1)
+
+/* SPD_RSM_TIMSET */
+#define USB2_SPD_RSM_TIMSET_INIT 0x014e029b
+
+/* OC_TIMSET */
+#define USB2_OC_TIMSET_INIT 0x000209ab
+
+/* COMMCTRL */
+#define USB2_COMMCTRL_OTG_PERI BIT(31) /* 1 = Peripheral mode */
+
+/* OBINTSTA and OBINTEN */
+#define USB2_OBINT_SESSVLDCHG BIT(12)
+#define USB2_OBINT_IDDIGCHG BIT(11)
+#define USB2_OBINT_BITS (USB2_OBINT_SESSVLDCHG | \
+ USB2_OBINT_IDDIGCHG)
+
+/* VBCTRL */
+#define USB2_VBCTRL_OCCLREN BIT(16)
+#define USB2_VBCTRL_DRVVBUSSEL BIT(8)
+
+/* LINECTRL1 */
+#define USB2_LINECTRL1_DPRPD_EN BIT(19)
+#define USB2_LINECTRL1_DP_RPD BIT(18)
+#define USB2_LINECTRL1_DMRPD_EN BIT(17)
+#define USB2_LINECTRL1_DM_RPD BIT(16)
+#define USB2_LINECTRL1_OPMODE_NODRV BIT(6)
+
+/* ADPCTRL */
+#define USB2_ADPCTRL_OTGSESSVLD BIT(20)
+#define USB2_ADPCTRL_IDDIG BIT(19)
+#define USB2_ADPCTRL_IDPULLUP BIT(5) /* 1 = ID sampling is enabled */
+#define USB2_ADPCTRL_DRVVBUS BIT(4)
+
+#define RCAR_GEN3_PHY_HAS_DEDICATED_PINS 1
+
+struct rcar_gen3_chan {
+ void __iomem *base;
+ struct extcon_dev *extcon;
+ struct phy *phy;
+ struct regulator *vbus;
+ struct work_struct work;
+ bool extcon_host;
+ bool has_otg_pins;
+};
+
+static void rcar_gen3_phy_usb2_work(struct work_struct *work)
+{
+ struct rcar_gen3_chan *ch = container_of(work, struct rcar_gen3_chan,
+ work);
+
+ if (ch->extcon_host) {
+ extcon_set_state_sync(ch->extcon, EXTCON_USB_HOST, true);
+ extcon_set_state_sync(ch->extcon, EXTCON_USB, false);
+ } else {
+ extcon_set_state_sync(ch->extcon, EXTCON_USB_HOST, false);
+ extcon_set_state_sync(ch->extcon, EXTCON_USB, true);
+ }
+}
+
+static void rcar_gen3_set_host_mode(struct rcar_gen3_chan *ch, int host)
+{
+ void __iomem *usb2_base = ch->base;
+ u32 val = readl(usb2_base + USB2_COMMCTRL);
+
+ dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, host);
+ if (host)
+ val &= ~USB2_COMMCTRL_OTG_PERI;
+ else
+ val |= USB2_COMMCTRL_OTG_PERI;
+ writel(val, usb2_base + USB2_COMMCTRL);
+}
+
+static void rcar_gen3_set_linectrl(struct rcar_gen3_chan *ch, int dp, int dm)
+{
+ void __iomem *usb2_base = ch->base;
+ u32 val = readl(usb2_base + USB2_LINECTRL1);
+
+ dev_vdbg(&ch->phy->dev, "%s: %08x, %d, %d\n", __func__, val, dp, dm);
+ val &= ~(USB2_LINECTRL1_DP_RPD | USB2_LINECTRL1_DM_RPD);
+ if (dp)
+ val |= USB2_LINECTRL1_DP_RPD;
+ if (dm)
+ val |= USB2_LINECTRL1_DM_RPD;
+ writel(val, usb2_base + USB2_LINECTRL1);
+}
+
+static void rcar_gen3_enable_vbus_ctrl(struct rcar_gen3_chan *ch, int vbus)
+{
+ void __iomem *usb2_base = ch->base;
+ u32 val = readl(usb2_base + USB2_ADPCTRL);
+
+ dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, vbus);
+ if (vbus)
+ val |= USB2_ADPCTRL_DRVVBUS;
+ else
+ val &= ~USB2_ADPCTRL_DRVVBUS;
+ writel(val, usb2_base + USB2_ADPCTRL);
+}
+
+static void rcar_gen3_init_for_host(struct rcar_gen3_chan *ch)
+{
+ rcar_gen3_set_linectrl(ch, 1, 1);
+ rcar_gen3_set_host_mode(ch, 1);
+ rcar_gen3_enable_vbus_ctrl(ch, 1);
+
+ ch->extcon_host = true;
+ schedule_work(&ch->work);
+}
+
+static void rcar_gen3_init_for_peri(struct rcar_gen3_chan *ch)
+{
+ rcar_gen3_set_linectrl(ch, 0, 1);
+ rcar_gen3_set_host_mode(ch, 0);
+ rcar_gen3_enable_vbus_ctrl(ch, 0);
+
+ ch->extcon_host = false;
+ schedule_work(&ch->work);
+}
+
+static void rcar_gen3_init_for_b_host(struct rcar_gen3_chan *ch)
+{
+ void __iomem *usb2_base = ch->base;
+ u32 val;
+
+ val = readl(usb2_base + USB2_LINECTRL1);
+ writel(val | USB2_LINECTRL1_OPMODE_NODRV, usb2_base + USB2_LINECTRL1);
+
+ rcar_gen3_set_linectrl(ch, 1, 1);
+ rcar_gen3_set_host_mode(ch, 1);
+ rcar_gen3_enable_vbus_ctrl(ch, 0);
+
+ val = readl(usb2_base + USB2_LINECTRL1);
+ writel(val & ~USB2_LINECTRL1_OPMODE_NODRV, usb2_base + USB2_LINECTRL1);
+}
+
+static void rcar_gen3_init_for_a_peri(struct rcar_gen3_chan *ch)
+{
+ rcar_gen3_set_linectrl(ch, 0, 1);
+ rcar_gen3_set_host_mode(ch, 0);
+ rcar_gen3_enable_vbus_ctrl(ch, 1);
+}
+
+static void rcar_gen3_init_from_a_peri_to_a_host(struct rcar_gen3_chan *ch)
+{
+ void __iomem *usb2_base = ch->base;
+ u32 val;
+
+ val = readl(usb2_base + USB2_OBINTEN);
+ writel(val & ~USB2_OBINT_BITS, usb2_base + USB2_OBINTEN);
+
+ rcar_gen3_enable_vbus_ctrl(ch, 1);
+ rcar_gen3_init_for_host(ch);
+
+ writel(val | USB2_OBINT_BITS, usb2_base + USB2_OBINTEN);
+}
+
+static bool rcar_gen3_check_id(struct rcar_gen3_chan *ch)
+{
+ return !!(readl(ch->base + USB2_ADPCTRL) & USB2_ADPCTRL_IDDIG);
+}
+
+static void rcar_gen3_device_recognition(struct rcar_gen3_chan *ch)
+{
+ if (!rcar_gen3_check_id(ch))
+ rcar_gen3_init_for_host(ch);
+ else
+ rcar_gen3_init_for_peri(ch);
+}
+
+static bool rcar_gen3_is_host(struct rcar_gen3_chan *ch)
+{
+ return !(readl(ch->base + USB2_COMMCTRL) & USB2_COMMCTRL_OTG_PERI);
+}
+
+static enum phy_mode rcar_gen3_get_phy_mode(struct rcar_gen3_chan *ch)
+{
+ if (rcar_gen3_is_host(ch))
+ return PHY_MODE_USB_HOST;
+
+ return PHY_MODE_USB_DEVICE;
+}
+
+static ssize_t role_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct rcar_gen3_chan *ch = dev_get_drvdata(dev);
+ bool is_b_device;
+ enum phy_mode cur_mode, new_mode;
+
+ if (!ch->has_otg_pins || !ch->phy->init_count)
+ return -EIO;
+
+ if (sysfs_streq(buf, "host"))
+ new_mode = PHY_MODE_USB_HOST;
+ else if (sysfs_streq(buf, "peripheral"))
+ new_mode = PHY_MODE_USB_DEVICE;
+ else
+ return -EINVAL;
+
+ /* is_b_device: true is B-Device. false is A-Device. */
+ is_b_device = rcar_gen3_check_id(ch);
+ cur_mode = rcar_gen3_get_phy_mode(ch);
+
+ /* If current and new mode is the same, this returns the error */
+ if (cur_mode == new_mode)
+ return -EINVAL;
+
+ if (new_mode == PHY_MODE_USB_HOST) { /* And is_host must be false */
+ if (!is_b_device) /* A-Peripheral */
+ rcar_gen3_init_from_a_peri_to_a_host(ch);
+ else /* B-Peripheral */
+ rcar_gen3_init_for_b_host(ch);
+ } else { /* And is_host must be true */
+ if (!is_b_device) /* A-Host */
+ rcar_gen3_init_for_a_peri(ch);
+ else /* B-Host */
+ rcar_gen3_init_for_peri(ch);
+ }
+
+ return count;
+}
+
+static ssize_t role_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct rcar_gen3_chan *ch = dev_get_drvdata(dev);
+
+ if (!ch->has_otg_pins || !ch->phy->init_count)
+ return -EIO;
+
+ return sprintf(buf, "%s\n", rcar_gen3_is_host(ch) ? "host" :
+ "peripheral");
+}
+static DEVICE_ATTR_RW(role);
+
+static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch)
+{
+ void __iomem *usb2_base = ch->base;
+ u32 val;
+
+ val = readl(usb2_base + USB2_VBCTRL);
+ val &= ~USB2_VBCTRL_OCCLREN;
+ writel(val | USB2_VBCTRL_DRVVBUSSEL, usb2_base + USB2_VBCTRL);
+ writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA);
+ val = readl(usb2_base + USB2_OBINTEN);
+ writel(val | USB2_OBINT_BITS, usb2_base + USB2_OBINTEN);
+ val = readl(usb2_base + USB2_ADPCTRL);
+ writel(val | USB2_ADPCTRL_IDPULLUP, usb2_base + USB2_ADPCTRL);
+ val = readl(usb2_base + USB2_LINECTRL1);
+ rcar_gen3_set_linectrl(ch, 0, 0);
+ writel(val | USB2_LINECTRL1_DPRPD_EN | USB2_LINECTRL1_DMRPD_EN,
+ usb2_base + USB2_LINECTRL1);
+
+ rcar_gen3_device_recognition(ch);
+}
+
+static int rcar_gen3_phy_usb2_init(struct phy *p)
+{
+ struct rcar_gen3_chan *channel = phy_get_drvdata(p);
+ void __iomem *usb2_base = channel->base;
+
+ /* Initialize USB2 part */
+ writel(USB2_INT_ENABLE_INIT, usb2_base + USB2_INT_ENABLE);
+ writel(USB2_SPD_RSM_TIMSET_INIT, usb2_base + USB2_SPD_RSM_TIMSET);
+ writel(USB2_OC_TIMSET_INIT, usb2_base + USB2_OC_TIMSET);
+
+ /* Initialize otg part */
+ if (channel->has_otg_pins)
+ rcar_gen3_init_otg(channel);
+
+ return 0;
+}
+
+static int rcar_gen3_phy_usb2_exit(struct phy *p)
+{
+ struct rcar_gen3_chan *channel = phy_get_drvdata(p);
+
+ writel(0, channel->base + USB2_INT_ENABLE);
+
+ return 0;
+}
+
+static int rcar_gen3_phy_usb2_power_on(struct phy *p)
+{
+ struct rcar_gen3_chan *channel = phy_get_drvdata(p);
+ void __iomem *usb2_base = channel->base;
+ u32 val;
+ int ret;
+
+ if (channel->vbus) {
+ ret = regulator_enable(channel->vbus);
+ if (ret)
+ return ret;
+ }
+
+ val = readl(usb2_base + USB2_USBCTR);
+ val |= USB2_USBCTR_PLL_RST;
+ writel(val, usb2_base + USB2_USBCTR);
+ val &= ~USB2_USBCTR_PLL_RST;
+ writel(val, usb2_base + USB2_USBCTR);
+
+ return 0;
+}
+
+static int rcar_gen3_phy_usb2_power_off(struct phy *p)
+{
+ struct rcar_gen3_chan *channel = phy_get_drvdata(p);
+ int ret = 0;
+
+ if (channel->vbus)
+ ret = regulator_disable(channel->vbus);
+
+ return ret;
+}
+
+static const struct phy_ops rcar_gen3_phy_usb2_ops = {
+ .init = rcar_gen3_phy_usb2_init,
+ .exit = rcar_gen3_phy_usb2_exit,
+ .power_on = rcar_gen3_phy_usb2_power_on,
+ .power_off = rcar_gen3_phy_usb2_power_off,
+ .owner = THIS_MODULE,
+};
+
+static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch)
+{
+ struct rcar_gen3_chan *ch = _ch;
+ void __iomem *usb2_base = ch->base;
+ u32 status = readl(usb2_base + USB2_OBINTSTA);
+ irqreturn_t ret = IRQ_NONE;
+
+ if (status & USB2_OBINT_BITS) {
+ dev_vdbg(&ch->phy->dev, "%s: %08x\n", __func__, status);
+ writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA);
+ rcar_gen3_device_recognition(ch);
+ ret = IRQ_HANDLED;
+ }
+
+ return ret;
+}
+
+static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = {
+ {
+ .compatible = "renesas,usb2-phy-r8a7795",
+ .data = (void *)RCAR_GEN3_PHY_HAS_DEDICATED_PINS,
+ },
+ {
+ .compatible = "renesas,usb2-phy-r8a7796",
+ .data = (void *)RCAR_GEN3_PHY_HAS_DEDICATED_PINS,
+ },
+ {
+ .compatible = "renesas,usb2-phy-r8a77965",
+ .data = (void *)RCAR_GEN3_PHY_HAS_DEDICATED_PINS,
+ },
+ {
+ .compatible = "renesas,rcar-gen3-usb2-phy",
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb2_match_table);
+
+static const unsigned int rcar_gen3_phy_cable[] = {
+ EXTCON_USB,
+ EXTCON_USB_HOST,
+ EXTCON_NONE,
+};
+
+static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rcar_gen3_chan *channel;
+ struct phy_provider *provider;
+ struct resource *res;
+ int irq, ret = 0;
+
+ if (!dev->of_node) {
+ dev_err(dev, "This driver needs device tree\n");
+ return -EINVAL;
+ }
+
+ channel = devm_kzalloc(dev, sizeof(*channel), GFP_KERNEL);
+ if (!channel)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ channel->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(channel->base))
+ return PTR_ERR(channel->base);
+
+ /* call request_irq for OTG */
+ irq = platform_get_irq(pdev, 0);
+ if (irq >= 0) {
+ INIT_WORK(&channel->work, rcar_gen3_phy_usb2_work);
+ irq = devm_request_irq(dev, irq, rcar_gen3_phy_usb2_irq,
+ IRQF_SHARED, dev_name(dev), channel);
+ if (irq < 0)
+ dev_err(dev, "No irq handler (%d)\n", irq);
+ }
+
+ if (of_usb_get_dr_mode_by_phy(dev->of_node, 0) == USB_DR_MODE_OTG) {
+ int ret;
+
+ channel->has_otg_pins = (uintptr_t)of_device_get_match_data(dev);
+ channel->extcon = devm_extcon_dev_allocate(dev,
+ rcar_gen3_phy_cable);
+ if (IS_ERR(channel->extcon))
+ return PTR_ERR(channel->extcon);
+
+ ret = devm_extcon_dev_register(dev, channel->extcon);
+ if (ret < 0) {
+ dev_err(dev, "Failed to register extcon\n");
+ return ret;
+ }
+ }
+
+ /*
+ * devm_phy_create() will call pm_runtime_enable(&phy->dev);
+ * And then, phy-core will manage runtime pm for this device.
+ */
+ pm_runtime_enable(dev);
+ channel->phy = devm_phy_create(dev, NULL, &rcar_gen3_phy_usb2_ops);
+ if (IS_ERR(channel->phy)) {
+ dev_err(dev, "Failed to create USB2 PHY\n");
+ ret = PTR_ERR(channel->phy);
+ goto error;
+ }
+
+ channel->vbus = devm_regulator_get_optional(dev, "vbus");
+ if (IS_ERR(channel->vbus)) {
+ if (PTR_ERR(channel->vbus) == -EPROBE_DEFER) {
+ ret = PTR_ERR(channel->vbus);
+ goto error;
+ }
+ channel->vbus = NULL;
+ }
+
+ platform_set_drvdata(pdev, channel);
+ phy_set_drvdata(channel->phy, channel);
+
+ provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(provider)) {
+ dev_err(dev, "Failed to register PHY provider\n");
+ ret = PTR_ERR(provider);
+ goto error;
+ } else if (channel->has_otg_pins) {
+ int ret;
+
+ ret = device_create_file(dev, &dev_attr_role);
+ if (ret < 0)
+ goto error;
+ }
+
+ return 0;
+
+error:
+ pm_runtime_disable(dev);
+
+ return ret;
+}
+
+static int rcar_gen3_phy_usb2_remove(struct platform_device *pdev)
+{
+ struct rcar_gen3_chan *channel = platform_get_drvdata(pdev);
+
+ if (channel->has_otg_pins)
+ device_remove_file(&pdev->dev, &dev_attr_role);
+
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+};
+
+static struct platform_driver rcar_gen3_phy_usb2_driver = {
+ .driver = {
+ .name = "phy_rcar_gen3_usb2",
+ .of_match_table = rcar_gen3_phy_usb2_match_table,
+ },
+ .probe = rcar_gen3_phy_usb2_probe,
+ .remove = rcar_gen3_phy_usb2_remove,
+};
+module_platform_driver(rcar_gen3_phy_usb2_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Renesas R-Car Gen3 USB 2.0 PHY");
+MODULE_AUTHOR("Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>");
diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb3.c b/drivers/phy/renesas/phy-rcar-gen3-usb3.c
new file mode 100644
index 000000000..88c83c9b8
--- /dev/null
+++ b/drivers/phy/renesas/phy-rcar-gen3-usb3.c
@@ -0,0 +1,226 @@
+/*
+ * Renesas R-Car Gen3 for USB3.0 PHY driver
+ *
+ * Copyright (C) 2017 Renesas Electronics Corporation
+ *
+ * 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/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#define USB30_CLKSET0 0x034
+#define USB30_CLKSET1 0x036
+#define USB30_SSC_SET 0x038
+#define USB30_PHY_ENABLE 0x060
+#define USB30_VBUS_EN 0x064
+
+/* USB30_CLKSET0 */
+#define CLKSET0_PRIVATE 0x05c0
+#define CLKSET0_USB30_FSEL_USB_EXTAL 0x0002
+
+/* USB30_CLKSET1 */
+#define CLKSET1_USB30_PLL_MULTI_SHIFT 6
+#define CLKSET1_USB30_PLL_MULTI_USB_EXTAL (0x64 << \
+ CLKSET1_USB30_PLL_MULTI_SHIFT)
+#define CLKSET1_PHYRESET BIT(4) /* 1: reset */
+#define CLKSET1_REF_CLKDIV BIT(3) /* 1: USB_EXTAL */
+#define CLKSET1_PRIVATE_2_1 BIT(1) /* Write B'01 */
+#define CLKSET1_REF_CLK_SEL BIT(0) /* 1: USB3S0_CLK_P */
+
+/* USB30_SSC_SET */
+#define SSC_SET_SSC_EN BIT(12)
+#define SSC_SET_RANGE_SHIFT 9
+#define SSC_SET_RANGE_4980 (0x0 << SSC_SET_RANGE_SHIFT)
+#define SSC_SET_RANGE_4492 (0x1 << SSC_SET_RANGE_SHIFT)
+#define SSC_SET_RANGE_4003 (0x2 << SSC_SET_RANGE_SHIFT)
+
+/* USB30_PHY_ENABLE */
+#define PHY_ENABLE_RESET_EN BIT(4)
+
+/* USB30_VBUS_EN */
+#define VBUS_EN_VBUS_EN BIT(1)
+
+struct rcar_gen3_usb3 {
+ void __iomem *base;
+ struct phy *phy;
+ u32 ssc_range;
+ bool usb3s_clk;
+ bool usb_extal;
+};
+
+static void write_clkset1_for_usb_extal(struct rcar_gen3_usb3 *r, bool reset)
+{
+ u16 val = CLKSET1_USB30_PLL_MULTI_USB_EXTAL |
+ CLKSET1_REF_CLKDIV | CLKSET1_PRIVATE_2_1;
+
+ if (reset)
+ val |= CLKSET1_PHYRESET;
+
+ writew(val, r->base + USB30_CLKSET1);
+}
+
+static void rcar_gen3_phy_usb3_enable_ssc(struct rcar_gen3_usb3 *r)
+{
+ u16 val = SSC_SET_SSC_EN;
+
+ switch (r->ssc_range) {
+ case 4980:
+ val |= SSC_SET_RANGE_4980;
+ break;
+ case 4492:
+ val |= SSC_SET_RANGE_4492;
+ break;
+ case 4003:
+ val |= SSC_SET_RANGE_4003;
+ break;
+ default:
+ dev_err(&r->phy->dev, "%s: unsupported range (%x)\n", __func__,
+ r->ssc_range);
+ return;
+ }
+
+ writew(val, r->base + USB30_SSC_SET);
+}
+
+static void rcar_gen3_phy_usb3_select_usb_extal(struct rcar_gen3_usb3 *r)
+{
+ write_clkset1_for_usb_extal(r, false);
+ if (r->ssc_range)
+ rcar_gen3_phy_usb3_enable_ssc(r);
+ writew(CLKSET0_PRIVATE | CLKSET0_USB30_FSEL_USB_EXTAL,
+ r->base + USB30_CLKSET0);
+ writew(PHY_ENABLE_RESET_EN, r->base + USB30_PHY_ENABLE);
+ write_clkset1_for_usb_extal(r, true);
+ usleep_range(10, 20);
+ write_clkset1_for_usb_extal(r, false);
+}
+
+static int rcar_gen3_phy_usb3_init(struct phy *p)
+{
+ struct rcar_gen3_usb3 *r = phy_get_drvdata(p);
+
+ dev_vdbg(&r->phy->dev, "%s: enter (%d, %d, %d)\n", __func__,
+ r->usb3s_clk, r->usb_extal, r->ssc_range);
+
+ if (!r->usb3s_clk && r->usb_extal)
+ rcar_gen3_phy_usb3_select_usb_extal(r);
+
+ /* Enables VBUS detection anyway */
+ writew(VBUS_EN_VBUS_EN, r->base + USB30_VBUS_EN);
+
+ return 0;
+}
+
+static const struct phy_ops rcar_gen3_phy_usb3_ops = {
+ .init = rcar_gen3_phy_usb3_init,
+ .owner = THIS_MODULE,
+};
+
+static const struct of_device_id rcar_gen3_phy_usb3_match_table[] = {
+ { .compatible = "renesas,rcar-gen3-usb3-phy" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb3_match_table);
+
+static int rcar_gen3_phy_usb3_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rcar_gen3_usb3 *r;
+ struct phy_provider *provider;
+ struct resource *res;
+ int ret = 0;
+ struct clk *clk;
+
+ if (!dev->of_node) {
+ dev_err(dev, "This driver needs device tree\n");
+ return -EINVAL;
+ }
+
+ r = devm_kzalloc(dev, sizeof(*r), GFP_KERNEL);
+ if (!r)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ r->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(r->base))
+ return PTR_ERR(r->base);
+
+ clk = devm_clk_get(dev, "usb3s_clk");
+ if (!IS_ERR(clk) && !clk_prepare_enable(clk)) {
+ r->usb3s_clk = !!clk_get_rate(clk);
+ clk_disable_unprepare(clk);
+ }
+ clk = devm_clk_get(dev, "usb_extal");
+ if (!IS_ERR(clk) && !clk_prepare_enable(clk)) {
+ r->usb_extal = !!clk_get_rate(clk);
+ clk_disable_unprepare(clk);
+ }
+
+ if (!r->usb3s_clk && !r->usb_extal) {
+ dev_err(dev, "This driver needs usb3s_clk and/or usb_extal\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ /*
+ * devm_phy_create() will call pm_runtime_enable(&phy->dev);
+ * And then, phy-core will manage runtime pm for this device.
+ */
+ pm_runtime_enable(dev);
+
+ r->phy = devm_phy_create(dev, NULL, &rcar_gen3_phy_usb3_ops);
+ if (IS_ERR(r->phy)) {
+ dev_err(dev, "Failed to create USB3 PHY\n");
+ ret = PTR_ERR(r->phy);
+ goto error;
+ }
+
+ of_property_read_u32(dev->of_node, "renesas,ssc-range", &r->ssc_range);
+
+ platform_set_drvdata(pdev, r);
+ phy_set_drvdata(r->phy, r);
+
+ provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(provider)) {
+ dev_err(dev, "Failed to register PHY provider\n");
+ ret = PTR_ERR(provider);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ pm_runtime_disable(dev);
+
+ return ret;
+}
+
+static int rcar_gen3_phy_usb3_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+};
+
+static struct platform_driver rcar_gen3_phy_usb3_driver = {
+ .driver = {
+ .name = "phy_rcar_gen3_usb3",
+ .of_match_table = rcar_gen3_phy_usb3_match_table,
+ },
+ .probe = rcar_gen3_phy_usb3_probe,
+ .remove = rcar_gen3_phy_usb3_remove,
+};
+module_platform_driver(rcar_gen3_phy_usb3_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Renesas R-Car Gen3 USB 3.0 PHY");
+MODULE_AUTHOR("Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>");
diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig
new file mode 100644
index 000000000..0e15119dd
--- /dev/null
+++ b/drivers/phy/rockchip/Kconfig
@@ -0,0 +1,52 @@
+#
+# Phy drivers for Rockchip platforms
+#
+config PHY_ROCKCHIP_DP
+ tristate "Rockchip Display Port PHY Driver"
+ depends on ARCH_ROCKCHIP && OF
+ select GENERIC_PHY
+ help
+ Enable this to support the Rockchip Display Port PHY.
+
+config PHY_ROCKCHIP_EMMC
+ tristate "Rockchip EMMC PHY Driver"
+ depends on ARCH_ROCKCHIP && OF
+ select GENERIC_PHY
+ help
+ Enable this to support the Rockchip EMMC PHY.
+
+config PHY_ROCKCHIP_INNO_USB2
+ tristate "Rockchip INNO USB2PHY Driver"
+ depends on (ARCH_ROCKCHIP || COMPILE_TEST) && OF
+ depends on COMMON_CLK
+ depends on EXTCON
+ depends on USB_SUPPORT
+ select GENERIC_PHY
+ select USB_COMMON
+ help
+ Support for Rockchip USB2.0 PHY with Innosilicon IP block.
+
+config PHY_ROCKCHIP_PCIE
+ tristate "Rockchip PCIe PHY Driver"
+ depends on (ARCH_ROCKCHIP && OF) || COMPILE_TEST
+ depends on HAS_IOMEM
+ 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_ROCKCHIP_USB
+ tristate "Rockchip USB2 PHY Driver"
+ depends on ARCH_ROCKCHIP && OF
+ select GENERIC_PHY
+ help
+ Enable this to support the Rockchip USB 2.0 PHY.
diff --git a/drivers/phy/rockchip/Makefile b/drivers/phy/rockchip/Makefile
new file mode 100644
index 000000000..7f149d989
--- /dev/null
+++ b/drivers/phy/rockchip/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_PHY_ROCKCHIP_DP) += phy-rockchip-dp.o
+obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o
+obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o
+obj-$(CONFIG_PHY_ROCKCHIP_PCIE) += phy-rockchip-pcie.o
+obj-$(CONFIG_PHY_ROCKCHIP_TYPEC) += phy-rockchip-typec.o
+obj-$(CONFIG_PHY_ROCKCHIP_USB) += phy-rockchip-usb.o
diff --git a/drivers/phy/rockchip/phy-rockchip-dp.c b/drivers/phy/rockchip/phy-rockchip-dp.c
new file mode 100644
index 000000000..8b267a746
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-dp.c
@@ -0,0 +1,154 @@
+/*
+ * Rockchip DP PHY driver
+ *
+ * Copyright (C) 2016 FuZhou Rockchip Co., Ltd.
+ * Author: Yakir Yang <ykk@@rock-chips.com>
+ *
+ * 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.
+ */
+
+#include <linux/clk.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define GRF_SOC_CON12 0x0274
+
+#define GRF_EDP_REF_CLK_SEL_INTER_HIWORD_MASK BIT(20)
+#define GRF_EDP_REF_CLK_SEL_INTER BIT(4)
+
+#define GRF_EDP_PHY_SIDDQ_HIWORD_MASK BIT(21)
+#define GRF_EDP_PHY_SIDDQ_ON 0
+#define GRF_EDP_PHY_SIDDQ_OFF BIT(5)
+
+struct rockchip_dp_phy {
+ struct device *dev;
+ struct regmap *grf;
+ struct clk *phy_24m;
+};
+
+static int rockchip_set_phy_state(struct phy *phy, bool enable)
+{
+ struct rockchip_dp_phy *dp = phy_get_drvdata(phy);
+ int ret;
+
+ if (enable) {
+ ret = regmap_write(dp->grf, GRF_SOC_CON12,
+ GRF_EDP_PHY_SIDDQ_HIWORD_MASK |
+ GRF_EDP_PHY_SIDDQ_ON);
+ if (ret < 0) {
+ dev_err(dp->dev, "Can't enable PHY power %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(dp->phy_24m);
+ } else {
+ clk_disable_unprepare(dp->phy_24m);
+
+ ret = regmap_write(dp->grf, GRF_SOC_CON12,
+ GRF_EDP_PHY_SIDDQ_HIWORD_MASK |
+ GRF_EDP_PHY_SIDDQ_OFF);
+ }
+
+ return ret;
+}
+
+static int rockchip_dp_phy_power_on(struct phy *phy)
+{
+ return rockchip_set_phy_state(phy, true);
+}
+
+static int rockchip_dp_phy_power_off(struct phy *phy)
+{
+ return rockchip_set_phy_state(phy, false);
+}
+
+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 rockchip_dp_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct phy_provider *phy_provider;
+ struct rockchip_dp_phy *dp;
+ struct phy *phy;
+ int ret;
+
+ if (!np)
+ return -ENODEV;
+
+ if (!dev->parent || !dev->parent->of_node)
+ return -ENODEV;
+
+ dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
+ if (!dp)
+ return -ENOMEM;
+
+ dp->dev = dev;
+
+ dp->phy_24m = devm_clk_get(dev, "24m");
+ if (IS_ERR(dp->phy_24m)) {
+ dev_err(dev, "cannot get clock 24m\n");
+ return PTR_ERR(dp->phy_24m);
+ }
+
+ ret = clk_set_rate(dp->phy_24m, 24000000);
+ if (ret < 0) {
+ dev_err(dp->dev, "cannot set clock phy_24m %d\n", ret);
+ return ret;
+ }
+
+ dp->grf = syscon_node_to_regmap(dev->parent->of_node);
+ if (IS_ERR(dp->grf)) {
+ dev_err(dev, "rk3288-dp needs the General Register Files syscon\n");
+ return PTR_ERR(dp->grf);
+ }
+
+ ret = regmap_write(dp->grf, GRF_SOC_CON12, GRF_EDP_REF_CLK_SEL_INTER |
+ GRF_EDP_REF_CLK_SEL_INTER_HIWORD_MASK);
+ if (ret != 0) {
+ dev_err(dp->dev, "Could not config GRF edp ref clk: %d\n", ret);
+ return ret;
+ }
+
+ phy = devm_phy_create(dev, np, &rockchip_dp_phy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create phy\n");
+ return PTR_ERR(phy);
+ }
+ phy_set_drvdata(phy, dp);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id rockchip_dp_phy_dt_ids[] = {
+ { .compatible = "rockchip,rk3288-dp-phy" },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, rockchip_dp_phy_dt_ids);
+
+static struct platform_driver rockchip_dp_phy_driver = {
+ .probe = rockchip_dp_phy_probe,
+ .driver = {
+ .name = "rockchip-dp-phy",
+ .of_match_table = rockchip_dp_phy_dt_ids,
+ },
+};
+
+module_platform_driver(rockchip_dp_phy_driver);
+
+MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
+MODULE_DESCRIPTION("Rockchip DP PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/rockchip/phy-rockchip-emmc.c b/drivers/phy/rockchip/phy-rockchip-emmc.c
new file mode 100644
index 000000000..b237360f9
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-emmc.c
@@ -0,0 +1,379 @@
+/*
+ * Rockchip emmc 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/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.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))
+
+/* Register definition */
+#define GRF_EMMCPHY_CON0 0x0
+#define GRF_EMMCPHY_CON1 0x4
+#define GRF_EMMCPHY_CON2 0x8
+#define GRF_EMMCPHY_CON3 0xc
+#define GRF_EMMCPHY_CON4 0x10
+#define GRF_EMMCPHY_CON5 0x14
+#define GRF_EMMCPHY_CON6 0x18
+#define GRF_EMMCPHY_STATUS 0x20
+
+#define PHYCTRL_PDB_MASK 0x1
+#define PHYCTRL_PDB_SHIFT 0x0
+#define PHYCTRL_PDB_PWR_ON 0x1
+#define PHYCTRL_PDB_PWR_OFF 0x0
+#define PHYCTRL_ENDLL_MASK 0x1
+#define PHYCTRL_ENDLL_SHIFT 0x1
+#define PHYCTRL_ENDLL_ENABLE 0x1
+#define PHYCTRL_ENDLL_DISABLE 0x0
+#define PHYCTRL_CALDONE_MASK 0x1
+#define PHYCTRL_CALDONE_SHIFT 0x6
+#define PHYCTRL_CALDONE_DONE 0x1
+#define PHYCTRL_CALDONE_GOING 0x0
+#define PHYCTRL_DLLRDY_MASK 0x1
+#define PHYCTRL_DLLRDY_SHIFT 0x5
+#define PHYCTRL_DLLRDY_DONE 0x1
+#define PHYCTRL_DLLRDY_GOING 0x0
+#define PHYCTRL_FREQSEL_200M 0x0
+#define PHYCTRL_FREQSEL_50M 0x1
+#define PHYCTRL_FREQSEL_100M 0x2
+#define PHYCTRL_FREQSEL_150M 0x3
+#define PHYCTRL_FREQSEL_MASK 0x3
+#define PHYCTRL_FREQSEL_SHIFT 0xc
+#define PHYCTRL_DR_MASK 0x7
+#define PHYCTRL_DR_SHIFT 0x4
+#define PHYCTRL_DR_50OHM 0x0
+#define PHYCTRL_DR_33OHM 0x1
+#define PHYCTRL_DR_66OHM 0x2
+#define PHYCTRL_DR_100OHM 0x3
+#define PHYCTRL_DR_40OHM 0x4
+#define PHYCTRL_OTAPDLYENA 0x1
+#define PHYCTRL_OTAPDLYENA_MASK 0x1
+#define PHYCTRL_OTAPDLYENA_SHIFT 0xb
+#define PHYCTRL_OTAPDLYSEL_MASK 0xf
+#define PHYCTRL_OTAPDLYSEL_SHIFT 0x7
+
+#define PHYCTRL_IS_CALDONE(x) \
+ ((((x) >> PHYCTRL_CALDONE_SHIFT) & \
+ PHYCTRL_CALDONE_MASK) == PHYCTRL_CALDONE_DONE)
+#define PHYCTRL_IS_DLLRDY(x) \
+ ((((x) >> PHYCTRL_DLLRDY_SHIFT) & \
+ PHYCTRL_DLLRDY_MASK) == PHYCTRL_DLLRDY_DONE)
+
+struct rockchip_emmc_phy {
+ unsigned int reg_offset;
+ struct regmap *reg_base;
+ struct clk *emmcclk;
+};
+
+static int rockchip_emmc_phy_power(struct phy *phy, bool on_off)
+{
+ struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy);
+ unsigned int caldone;
+ unsigned int dllrdy;
+ unsigned int freqsel = PHYCTRL_FREQSEL_200M;
+ unsigned long rate;
+ int ret;
+
+ /*
+ * Keep phyctrl_pdb and phyctrl_endll low to allow
+ * initialization of CALIO state M/C DFFs
+ */
+ regmap_write(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_CON6,
+ HIWORD_UPDATE(PHYCTRL_PDB_PWR_OFF,
+ PHYCTRL_PDB_MASK,
+ PHYCTRL_PDB_SHIFT));
+ regmap_write(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_CON6,
+ HIWORD_UPDATE(PHYCTRL_ENDLL_DISABLE,
+ PHYCTRL_ENDLL_MASK,
+ PHYCTRL_ENDLL_SHIFT));
+
+ /* Already finish power_off above */
+ if (on_off == PHYCTRL_PDB_PWR_OFF)
+ return 0;
+
+ rate = clk_get_rate(rk_phy->emmcclk);
+
+ if (rate != 0) {
+ unsigned long ideal_rate;
+ unsigned long diff;
+
+ switch (rate) {
+ case 1 ... 74999999:
+ ideal_rate = 50000000;
+ freqsel = PHYCTRL_FREQSEL_50M;
+ break;
+ case 75000000 ... 124999999:
+ ideal_rate = 100000000;
+ freqsel = PHYCTRL_FREQSEL_100M;
+ break;
+ case 125000000 ... 174999999:
+ ideal_rate = 150000000;
+ freqsel = PHYCTRL_FREQSEL_150M;
+ break;
+ default:
+ ideal_rate = 200000000;
+ break;
+ }
+
+ diff = (rate > ideal_rate) ?
+ rate - ideal_rate : ideal_rate - rate;
+
+ /*
+ * In order for tuning delays to be accurate we need to be
+ * pretty spot on for the DLL range, so warn if we're too
+ * far off. Also warn if we're above the 200 MHz max. Don't
+ * warn for really slow rates since we won't be tuning then.
+ */
+ if ((rate > 50000000 && diff > 15000000) || (rate > 200000000))
+ dev_warn(&phy->dev, "Unsupported rate: %lu\n", rate);
+ }
+
+ /*
+ * According to the user manual, calpad calibration
+ * cycle takes more than 2us without the minimal recommended
+ * value, so we may need a little margin here
+ */
+ udelay(3);
+ regmap_write(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_CON6,
+ HIWORD_UPDATE(PHYCTRL_PDB_PWR_ON,
+ PHYCTRL_PDB_MASK,
+ PHYCTRL_PDB_SHIFT));
+
+ /*
+ * According to the user manual, it asks driver to wait 5us for
+ * calpad busy trimming. However it is documented that this value is
+ * PVT(A.K.A process,voltage and temperature) relevant, so some
+ * failure cases are found which indicates we should be more tolerant
+ * to calpad busy trimming.
+ */
+ ret = regmap_read_poll_timeout(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_STATUS,
+ caldone, PHYCTRL_IS_CALDONE(caldone),
+ 0, 50);
+ if (ret) {
+ pr_err("%s: caldone failed, ret=%d\n", __func__, ret);
+ return ret;
+ }
+
+ /* Set the frequency of the DLL operation */
+ regmap_write(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_CON0,
+ HIWORD_UPDATE(freqsel, PHYCTRL_FREQSEL_MASK,
+ PHYCTRL_FREQSEL_SHIFT));
+
+ /* Turn on the DLL */
+ regmap_write(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_CON6,
+ HIWORD_UPDATE(PHYCTRL_ENDLL_ENABLE,
+ PHYCTRL_ENDLL_MASK,
+ PHYCTRL_ENDLL_SHIFT));
+
+ /*
+ * We turned on the DLL even though the rate was 0 because we the
+ * clock might be turned on later. ...but we can't wait for the DLL
+ * to lock when the rate is 0 because it will never lock with no
+ * input clock.
+ *
+ * Technically we should be checking the lock later when the clock
+ * is turned on, but for now we won't.
+ */
+ if (rate == 0)
+ return 0;
+
+ /*
+ * After enabling analog DLL circuits docs say that we need 10.2 us if
+ * our source clock is at 50 MHz and that lock time scales linearly
+ * with clock speed. If we are powering on the PHY and the card clock
+ * is super slow (like 100 kHZ) this could take as long as 5.1 ms as
+ * per the math: 10.2 us * (50000000 Hz / 100000 Hz) => 5.1 ms
+ * Hopefully we won't be running at 100 kHz, but we should still make
+ * sure we wait long enough.
+ *
+ * NOTE: There appear to be corner cases where the DLL seems to take
+ * extra long to lock for reasons that aren't understood. In some
+ * extreme cases we've seen it take up to over 10ms (!). We'll be
+ * generous and give it 50ms.
+ */
+ ret = regmap_read_poll_timeout(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_STATUS,
+ dllrdy, PHYCTRL_IS_DLLRDY(dllrdy),
+ 0, 50 * USEC_PER_MSEC);
+ if (ret) {
+ pr_err("%s: dllrdy failed. ret=%d\n", __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rockchip_emmc_phy_init(struct phy *phy)
+{
+ struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy);
+ int ret = 0;
+
+ /*
+ * We purposely get the clock here and not in probe to avoid the
+ * circular dependency problem. We expect:
+ * - PHY driver to probe
+ * - SDHCI driver to start probe
+ * - SDHCI driver to register it's clock
+ * - SDHCI driver to get the PHY
+ * - SDHCI driver to init the PHY
+ *
+ * The clock is optional, so upon any error we just set to NULL.
+ *
+ * NOTE: we don't do anything special for EPROBE_DEFER here. Given the
+ * above expected use case, EPROBE_DEFER isn't sensible to expect, so
+ * it's just like any other error.
+ */
+ rk_phy->emmcclk = clk_get(&phy->dev, "emmcclk");
+ if (IS_ERR(rk_phy->emmcclk)) {
+ dev_dbg(&phy->dev, "Error getting emmcclk: %d\n", ret);
+ rk_phy->emmcclk = NULL;
+ }
+
+ return ret;
+}
+
+static int rockchip_emmc_phy_exit(struct phy *phy)
+{
+ struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy);
+
+ clk_put(rk_phy->emmcclk);
+
+ return 0;
+}
+
+static int rockchip_emmc_phy_power_off(struct phy *phy)
+{
+ /* Power down emmc phy analog blocks */
+ return rockchip_emmc_phy_power(phy, PHYCTRL_PDB_PWR_OFF);
+}
+
+static int rockchip_emmc_phy_power_on(struct phy *phy)
+{
+ struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy);
+
+ /* Drive impedance: 50 Ohm */
+ regmap_write(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_CON6,
+ HIWORD_UPDATE(PHYCTRL_DR_50OHM,
+ PHYCTRL_DR_MASK,
+ PHYCTRL_DR_SHIFT));
+
+ /* Output tap delay: enable */
+ regmap_write(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_CON0,
+ HIWORD_UPDATE(PHYCTRL_OTAPDLYENA,
+ PHYCTRL_OTAPDLYENA_MASK,
+ PHYCTRL_OTAPDLYENA_SHIFT));
+
+ /* Output tap delay */
+ regmap_write(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_CON0,
+ HIWORD_UPDATE(4,
+ PHYCTRL_OTAPDLYSEL_MASK,
+ PHYCTRL_OTAPDLYSEL_SHIFT));
+
+ /* Power up emmc phy analog blocks */
+ return rockchip_emmc_phy_power(phy, PHYCTRL_PDB_PWR_ON);
+}
+
+static const struct phy_ops ops = {
+ .init = rockchip_emmc_phy_init,
+ .exit = rockchip_emmc_phy_exit,
+ .power_on = rockchip_emmc_phy_power_on,
+ .power_off = rockchip_emmc_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int rockchip_emmc_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rockchip_emmc_phy *rk_phy;
+ struct phy *generic_phy;
+ struct phy_provider *phy_provider;
+ struct regmap *grf;
+ unsigned int reg_offset;
+
+ if (!dev->parent || !dev->parent->of_node)
+ return -ENODEV;
+
+ grf = syscon_node_to_regmap(dev->parent->of_node);
+ if (IS_ERR(grf)) {
+ dev_err(dev, "Missing rockchip,grf property\n");
+ return PTR_ERR(grf);
+ }
+
+ rk_phy = devm_kzalloc(dev, sizeof(*rk_phy), GFP_KERNEL);
+ if (!rk_phy)
+ return -ENOMEM;
+
+ if (of_property_read_u32(dev->of_node, "reg", &reg_offset)) {
+ dev_err(dev, "missing reg property in node %s\n",
+ dev->of_node->name);
+ return -EINVAL;
+ }
+
+ rk_phy->reg_offset = reg_offset;
+ rk_phy->reg_base = grf;
+
+ 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 const struct of_device_id rockchip_emmc_phy_dt_ids[] = {
+ { .compatible = "rockchip,rk3399-emmc-phy" },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, rockchip_emmc_phy_dt_ids);
+
+static struct platform_driver rockchip_emmc_driver = {
+ .probe = rockchip_emmc_phy_probe,
+ .driver = {
+ .name = "rockchip-emmc-phy",
+ .of_match_table = rockchip_emmc_phy_dt_ids,
+ },
+};
+
+module_platform_driver(rockchip_emmc_driver);
+
+MODULE_AUTHOR("Shawn Lin <shawn.lin@rock-chips.com>");
+MODULE_DESCRIPTION("Rockchip EMMC PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c
new file mode 100644
index 000000000..5049dac79
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c
@@ -0,0 +1,1463 @@
+/*
+ * 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/extcon-provider.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/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/usb/of.h>
+#include <linux/usb/otg.h>
+
+#define BIT_WRITEABLE_SHIFT 16
+#define SCHEDULE_DELAY (60 * HZ)
+#define OTG_SCHEDULE_DELAY (2 * 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,
+};
+
+/**
+ * Different states involved in USB charger detection.
+ * USB_CHG_STATE_UNDEFINED USB charger is not connected or detection
+ * process is not yet started.
+ * USB_CHG_STATE_WAIT_FOR_DCD Waiting for Data pins contact.
+ * USB_CHG_STATE_DCD_DONE Data pin contact is detected.
+ * USB_CHG_STATE_PRIMARY_DONE Primary detection is completed (Detects
+ * between SDP and DCP/CDP).
+ * USB_CHG_STATE_SECONDARY_DONE Secondary detection is completed (Detects
+ * between DCP and CDP).
+ * USB_CHG_STATE_DETECTED USB charger type is determined.
+ */
+enum usb_chg_state {
+ USB_CHG_STATE_UNDEFINED = 0,
+ USB_CHG_STATE_WAIT_FOR_DCD,
+ USB_CHG_STATE_DCD_DONE,
+ USB_CHG_STATE_PRIMARY_DONE,
+ USB_CHG_STATE_SECONDARY_DONE,
+ USB_CHG_STATE_DETECTED,
+};
+
+static const unsigned int rockchip_usb2phy_extcon_cable[] = {
+ EXTCON_USB,
+ EXTCON_USB_HOST,
+ EXTCON_CHG_USB_SDP,
+ EXTCON_CHG_USB_CDP,
+ EXTCON_CHG_USB_DCP,
+ EXTCON_CHG_USB_SLOW,
+ EXTCON_NONE,
+};
+
+struct usb2phy_reg {
+ unsigned int offset;
+ unsigned int bitend;
+ unsigned int bitstart;
+ unsigned int disable;
+ unsigned int enable;
+};
+
+/**
+ * struct rockchip_chg_det_reg: usb charger detect registers
+ * @cp_det: charging port detected successfully.
+ * @dcp_det: dedicated charging port detected successfully.
+ * @dp_det: assert data pin connect successfully.
+ * @idm_sink_en: open dm sink curren.
+ * @idp_sink_en: open dp sink current.
+ * @idp_src_en: open dm source current.
+ * @rdm_pdwn_en: open dm pull down resistor.
+ * @vdm_src_en: open dm voltage source.
+ * @vdp_src_en: open dp voltage source.
+ * @opmode: utmi operational mode.
+ */
+struct rockchip_chg_det_reg {
+ struct usb2phy_reg cp_det;
+ struct usb2phy_reg dcp_det;
+ struct usb2phy_reg dp_det;
+ struct usb2phy_reg idm_sink_en;
+ struct usb2phy_reg idp_sink_en;
+ struct usb2phy_reg idp_src_en;
+ struct usb2phy_reg rdm_pdwn_en;
+ struct usb2phy_reg vdm_src_en;
+ struct usb2phy_reg vdp_src_en;
+ struct usb2phy_reg opmode;
+};
+
+/**
+ * struct rockchip_usb2phy_port_cfg: usb-phy port configuration.
+ * @phy_sus: phy suspend register.
+ * @bvalid_det_en: vbus valid rise detection enable register.
+ * @bvalid_det_st: vbus valid rise detection status register.
+ * @bvalid_det_clr: vbus valid rise detection clear register.
+ * @ls_det_en: linestate detection enable register.
+ * @ls_det_st: linestate detection state register.
+ * @ls_det_clr: linestate detection clear register.
+ * @utmi_avalid: utmi vbus avalid status register.
+ * @utmi_bvalid: utmi vbus bvalid status 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 bvalid_det_en;
+ struct usb2phy_reg bvalid_det_st;
+ struct usb2phy_reg bvalid_det_clr;
+ struct usb2phy_reg ls_det_en;
+ struct usb2phy_reg ls_det_st;
+ struct usb2phy_reg ls_det_clr;
+ struct usb2phy_reg utmi_avalid;
+ struct usb2phy_reg utmi_bvalid;
+ 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.
+ * @chg_det: charger detection registers.
+ */
+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];
+ const struct rockchip_chg_det_reg chg_det;
+};
+
+/**
+ * struct rockchip_usb2phy_port: usb-phy port data.
+ * @port_id: flag for otg port or host port.
+ * @suspended: phy suspended flag.
+ * @utmi_avalid: utmi avalid status usage flag.
+ * true - use avalid to get vbus status
+ * flase - use bvalid to get vbus status
+ * @vbus_attached: otg device vbus status.
+ * @bvalid_irq: IRQ number assigned for vbus valid rise detection.
+ * @ls_irq: IRQ number assigned for linestate detection.
+ * @otg_mux_irq: IRQ number which multiplex otg-id/otg-bvalid/linestate
+ * irqs to one irq in otg-port.
+ * @mutex: for register updating in sm_work.
+ * @chg_work: charge detect work.
+ * @otg_sm_work: OTG state machine work.
+ * @sm_work: HOST state machine work.
+ * @phy_cfg: port register configuration, assigned by driver data.
+ * @event_nb: hold event notification callback.
+ * @state: define OTG enumeration states before device reset.
+ * @mode: the dr_mode of the controller.
+ */
+struct rockchip_usb2phy_port {
+ struct phy *phy;
+ unsigned int port_id;
+ bool suspended;
+ bool utmi_avalid;
+ bool vbus_attached;
+ int bvalid_irq;
+ int ls_irq;
+ int otg_mux_irq;
+ struct mutex mutex;
+ struct delayed_work chg_work;
+ struct delayed_work otg_sm_work;
+ struct delayed_work sm_work;
+ const struct rockchip_usb2phy_port_cfg *port_cfg;
+ struct notifier_block event_nb;
+ enum usb_otg_state state;
+ enum usb_dr_mode mode;
+};
+
+/**
+ * struct rockchip_usb2phy: usb2.0 phy driver data.
+ * @grf: General Register Files regmap.
+ * @usbgrf: USB 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.
+ * @chg_state: states involved in USB charger detection.
+ * @chg_type: USB charger types.
+ * @dcd_retries: The retry count used to track Data contact
+ * detection process.
+ * @edev: extcon device for notification registration
+ * @phy_cfg: phy register configuration, assigned by driver data.
+ * @ports: phy port instance.
+ */
+struct rockchip_usb2phy {
+ struct device *dev;
+ struct regmap *grf;
+ struct regmap *usbgrf;
+ struct clk *clk;
+ struct clk *clk480m;
+ struct clk_hw clk480m_hw;
+ enum usb_chg_state chg_state;
+ enum power_supply_type chg_type;
+ u8 dcd_retries;
+ struct extcon_dev *edev;
+ const struct rockchip_usb2phy_cfg *phy_cfg;
+ struct rockchip_usb2phy_port ports[USB2PHY_NUM_PORTS];
+};
+
+static inline struct regmap *get_reg_base(struct rockchip_usb2phy *rphy)
+{
+ return rphy->usbgrf == NULL ? rphy->grf : rphy->usbgrf;
+}
+
+static inline int property_enable(struct regmap *base,
+ 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(base, reg->offset, val);
+}
+
+static inline bool property_enabled(struct regmap *base,
+ const struct usb2phy_reg *reg)
+{
+ int ret;
+ unsigned int tmp, orig;
+ unsigned int mask = GENMASK(reg->bitend, reg->bitstart);
+
+ ret = regmap_read(base, reg->offset, &orig);
+ if (ret)
+ return false;
+
+ tmp = (orig & mask) >> reg->bitstart;
+ return tmp == reg->enable;
+}
+
+static int rockchip_usb2phy_clk480m_prepare(struct clk_hw *hw)
+{
+ struct rockchip_usb2phy *rphy =
+ container_of(hw, struct rockchip_usb2phy, clk480m_hw);
+ struct regmap *base = get_reg_base(rphy);
+ int ret;
+
+ /* turn on 480m clk output if it is off */
+ if (!property_enabled(base, &rphy->phy_cfg->clkout_ctl)) {
+ ret = property_enable(base, &rphy->phy_cfg->clkout_ctl, true);
+ if (ret)
+ return ret;
+
+ /* waiting for the clk become stable */
+ usleep_range(1200, 1300);
+ }
+
+ return 0;
+}
+
+static void rockchip_usb2phy_clk480m_unprepare(struct clk_hw *hw)
+{
+ struct rockchip_usb2phy *rphy =
+ container_of(hw, struct rockchip_usb2phy, clk480m_hw);
+ struct regmap *base = get_reg_base(rphy);
+
+ /* turn off 480m clk output */
+ property_enable(base, &rphy->phy_cfg->clkout_ctl, false);
+}
+
+static int rockchip_usb2phy_clk480m_prepared(struct clk_hw *hw)
+{
+ struct rockchip_usb2phy *rphy =
+ container_of(hw, struct rockchip_usb2phy, clk480m_hw);
+ struct regmap *base = get_reg_base(rphy);
+
+ return property_enabled(base, &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 = {
+ .prepare = rockchip_usb2phy_clk480m_prepare,
+ .unprepare = rockchip_usb2phy_clk480m_unprepare,
+ .is_prepared = rockchip_usb2phy_clk480m_prepared,
+ .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_extcon_register(struct rockchip_usb2phy *rphy)
+{
+ int ret;
+ struct device_node *node = rphy->dev->of_node;
+ struct extcon_dev *edev;
+
+ if (of_property_read_bool(node, "extcon")) {
+ edev = extcon_get_edev_by_phandle(rphy->dev, 0);
+ if (IS_ERR(edev)) {
+ if (PTR_ERR(edev) != -EPROBE_DEFER)
+ dev_err(rphy->dev, "Invalid or missing extcon\n");
+ return PTR_ERR(edev);
+ }
+ } else {
+ /* Initialize extcon device */
+ edev = devm_extcon_dev_allocate(rphy->dev,
+ rockchip_usb2phy_extcon_cable);
+
+ if (IS_ERR(edev))
+ return -ENOMEM;
+
+ ret = devm_extcon_dev_register(rphy->dev, edev);
+ if (ret) {
+ dev_err(rphy->dev, "failed to register extcon device\n");
+ return ret;
+ }
+ }
+
+ rphy->edev = edev;
+
+ return 0;
+}
+
+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 = 0;
+
+ mutex_lock(&rport->mutex);
+
+ if (rport->port_id == USB2PHY_PORT_OTG) {
+ if (rport->mode != USB_DR_MODE_HOST &&
+ rport->mode != USB_DR_MODE_UNKNOWN) {
+ /* clear bvalid status and enable bvalid detect irq */
+ ret = property_enable(rphy->grf,
+ &rport->port_cfg->bvalid_det_clr,
+ true);
+ if (ret)
+ goto out;
+
+ ret = property_enable(rphy->grf,
+ &rport->port_cfg->bvalid_det_en,
+ true);
+ if (ret)
+ goto out;
+
+ schedule_delayed_work(&rport->otg_sm_work,
+ OTG_SCHEDULE_DELAY * 3);
+ } else {
+ /* If OTG works in host only mode, do nothing. */
+ dev_dbg(&rport->phy->dev, "mode %d\n", rport->mode);
+ }
+ } else if (rport->port_id == USB2PHY_PORT_HOST) {
+ /* clear linestate and enable linestate detect irq */
+ ret = property_enable(rphy->grf,
+ &rport->port_cfg->ls_det_clr, true);
+ if (ret)
+ goto out;
+
+ ret = property_enable(rphy->grf,
+ &rport->port_cfg->ls_det_en, true);
+ if (ret)
+ goto out;
+
+ schedule_delayed_work(&rport->sm_work, SCHEDULE_DELAY);
+ }
+
+out:
+ mutex_unlock(&rport->mutex);
+ return ret;
+}
+
+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);
+ struct regmap *base = get_reg_base(rphy);
+ 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(base, &rport->port_cfg->phy_sus, false);
+ if (ret)
+ return ret;
+
+ /* waiting for the utmi_clk to become stable */
+ usleep_range(1500, 2000);
+
+ 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);
+ struct regmap *base = get_reg_base(rphy);
+ int ret;
+
+ dev_dbg(&rport->phy->dev, "port power off\n");
+
+ if (rport->suspended)
+ return 0;
+
+ ret = property_enable(base, &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_OTG &&
+ rport->mode != USB_DR_MODE_HOST &&
+ rport->mode != USB_DR_MODE_UNKNOWN) {
+ cancel_delayed_work_sync(&rport->otg_sm_work);
+ cancel_delayed_work_sync(&rport->chg_work);
+ } else 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,
+};
+
+static void rockchip_usb2phy_otg_sm_work(struct work_struct *work)
+{
+ struct rockchip_usb2phy_port *rport =
+ container_of(work, struct rockchip_usb2phy_port,
+ otg_sm_work.work);
+ struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent);
+ static unsigned int cable;
+ unsigned long delay;
+ bool vbus_attach, sch_work, notify_charger;
+
+ if (rport->utmi_avalid)
+ vbus_attach = property_enabled(rphy->grf,
+ &rport->port_cfg->utmi_avalid);
+ else
+ vbus_attach = property_enabled(rphy->grf,
+ &rport->port_cfg->utmi_bvalid);
+
+ sch_work = false;
+ notify_charger = false;
+ delay = OTG_SCHEDULE_DELAY;
+ dev_dbg(&rport->phy->dev, "%s otg sm work\n",
+ usb_otg_state_string(rport->state));
+
+ switch (rport->state) {
+ case OTG_STATE_UNDEFINED:
+ rport->state = OTG_STATE_B_IDLE;
+ if (!vbus_attach)
+ rockchip_usb2phy_power_off(rport->phy);
+ /* fall through */
+ case OTG_STATE_B_IDLE:
+ if (extcon_get_state(rphy->edev, EXTCON_USB_HOST) > 0) {
+ dev_dbg(&rport->phy->dev, "usb otg host connect\n");
+ rport->state = OTG_STATE_A_HOST;
+ rockchip_usb2phy_power_on(rport->phy);
+ return;
+ } else if (vbus_attach) {
+ dev_dbg(&rport->phy->dev, "vbus_attach\n");
+ switch (rphy->chg_state) {
+ case USB_CHG_STATE_UNDEFINED:
+ schedule_delayed_work(&rport->chg_work, 0);
+ return;
+ case USB_CHG_STATE_DETECTED:
+ switch (rphy->chg_type) {
+ case POWER_SUPPLY_TYPE_USB:
+ dev_dbg(&rport->phy->dev, "sdp cable is connected\n");
+ rockchip_usb2phy_power_on(rport->phy);
+ rport->state = OTG_STATE_B_PERIPHERAL;
+ notify_charger = true;
+ sch_work = true;
+ cable = EXTCON_CHG_USB_SDP;
+ break;
+ case POWER_SUPPLY_TYPE_USB_DCP:
+ dev_dbg(&rport->phy->dev, "dcp cable is connected\n");
+ rockchip_usb2phy_power_off(rport->phy);
+ notify_charger = true;
+ sch_work = true;
+ cable = EXTCON_CHG_USB_DCP;
+ break;
+ case POWER_SUPPLY_TYPE_USB_CDP:
+ dev_dbg(&rport->phy->dev, "cdp cable is connected\n");
+ rockchip_usb2phy_power_on(rport->phy);
+ rport->state = OTG_STATE_B_PERIPHERAL;
+ notify_charger = true;
+ sch_work = true;
+ cable = EXTCON_CHG_USB_CDP;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ } else {
+ notify_charger = true;
+ rphy->chg_state = USB_CHG_STATE_UNDEFINED;
+ rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN;
+ }
+
+ if (rport->vbus_attached != vbus_attach) {
+ rport->vbus_attached = vbus_attach;
+
+ if (notify_charger && rphy->edev) {
+ extcon_set_state_sync(rphy->edev,
+ cable, vbus_attach);
+ if (cable == EXTCON_CHG_USB_SDP)
+ extcon_set_state_sync(rphy->edev,
+ EXTCON_USB,
+ vbus_attach);
+ }
+ }
+ break;
+ case OTG_STATE_B_PERIPHERAL:
+ if (!vbus_attach) {
+ dev_dbg(&rport->phy->dev, "usb disconnect\n");
+ rphy->chg_state = USB_CHG_STATE_UNDEFINED;
+ rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN;
+ rport->state = OTG_STATE_B_IDLE;
+ delay = 0;
+ rockchip_usb2phy_power_off(rport->phy);
+ }
+ sch_work = true;
+ break;
+ case OTG_STATE_A_HOST:
+ if (extcon_get_state(rphy->edev, EXTCON_USB_HOST) == 0) {
+ dev_dbg(&rport->phy->dev, "usb otg host disconnect\n");
+ rport->state = OTG_STATE_B_IDLE;
+ rockchip_usb2phy_power_off(rport->phy);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (sch_work)
+ schedule_delayed_work(&rport->otg_sm_work, delay);
+}
+
+static const char *chg_to_string(enum power_supply_type chg_type)
+{
+ switch (chg_type) {
+ case POWER_SUPPLY_TYPE_USB:
+ return "USB_SDP_CHARGER";
+ case POWER_SUPPLY_TYPE_USB_DCP:
+ return "USB_DCP_CHARGER";
+ case POWER_SUPPLY_TYPE_USB_CDP:
+ return "USB_CDP_CHARGER";
+ default:
+ return "INVALID_CHARGER";
+ }
+}
+
+static void rockchip_chg_enable_dcd(struct rockchip_usb2phy *rphy,
+ bool en)
+{
+ struct regmap *base = get_reg_base(rphy);
+
+ property_enable(base, &rphy->phy_cfg->chg_det.rdm_pdwn_en, en);
+ property_enable(base, &rphy->phy_cfg->chg_det.idp_src_en, en);
+}
+
+static void rockchip_chg_enable_primary_det(struct rockchip_usb2phy *rphy,
+ bool en)
+{
+ struct regmap *base = get_reg_base(rphy);
+
+ property_enable(base, &rphy->phy_cfg->chg_det.vdp_src_en, en);
+ property_enable(base, &rphy->phy_cfg->chg_det.idm_sink_en, en);
+}
+
+static void rockchip_chg_enable_secondary_det(struct rockchip_usb2phy *rphy,
+ bool en)
+{
+ struct regmap *base = get_reg_base(rphy);
+
+ property_enable(base, &rphy->phy_cfg->chg_det.vdm_src_en, en);
+ property_enable(base, &rphy->phy_cfg->chg_det.idp_sink_en, en);
+}
+
+#define CHG_DCD_POLL_TIME (100 * HZ / 1000)
+#define CHG_DCD_MAX_RETRIES 6
+#define CHG_PRIMARY_DET_TIME (40 * HZ / 1000)
+#define CHG_SECONDARY_DET_TIME (40 * HZ / 1000)
+static void rockchip_chg_detect_work(struct work_struct *work)
+{
+ struct rockchip_usb2phy_port *rport =
+ container_of(work, struct rockchip_usb2phy_port, chg_work.work);
+ struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent);
+ struct regmap *base = get_reg_base(rphy);
+ bool is_dcd, tmout, vout;
+ unsigned long delay;
+
+ dev_dbg(&rport->phy->dev, "chg detection work state = %d\n",
+ rphy->chg_state);
+ switch (rphy->chg_state) {
+ case USB_CHG_STATE_UNDEFINED:
+ if (!rport->suspended)
+ rockchip_usb2phy_power_off(rport->phy);
+ /* put the controller in non-driving mode */
+ property_enable(base, &rphy->phy_cfg->chg_det.opmode, false);
+ /* Start DCD processing stage 1 */
+ rockchip_chg_enable_dcd(rphy, true);
+ rphy->chg_state = USB_CHG_STATE_WAIT_FOR_DCD;
+ rphy->dcd_retries = 0;
+ delay = CHG_DCD_POLL_TIME;
+ break;
+ case USB_CHG_STATE_WAIT_FOR_DCD:
+ /* get data contact detection status */
+ is_dcd = property_enabled(rphy->grf,
+ &rphy->phy_cfg->chg_det.dp_det);
+ tmout = ++rphy->dcd_retries == CHG_DCD_MAX_RETRIES;
+ /* stage 2 */
+ if (is_dcd || tmout) {
+ /* stage 4 */
+ /* Turn off DCD circuitry */
+ rockchip_chg_enable_dcd(rphy, false);
+ /* Voltage Source on DP, Probe on DM */
+ rockchip_chg_enable_primary_det(rphy, true);
+ delay = CHG_PRIMARY_DET_TIME;
+ rphy->chg_state = USB_CHG_STATE_DCD_DONE;
+ } else {
+ /* stage 3 */
+ delay = CHG_DCD_POLL_TIME;
+ }
+ break;
+ case USB_CHG_STATE_DCD_DONE:
+ vout = property_enabled(rphy->grf,
+ &rphy->phy_cfg->chg_det.cp_det);
+ rockchip_chg_enable_primary_det(rphy, false);
+ if (vout) {
+ /* Voltage Source on DM, Probe on DP */
+ rockchip_chg_enable_secondary_det(rphy, true);
+ delay = CHG_SECONDARY_DET_TIME;
+ rphy->chg_state = USB_CHG_STATE_PRIMARY_DONE;
+ } else {
+ if (rphy->dcd_retries == CHG_DCD_MAX_RETRIES) {
+ /* floating charger found */
+ rphy->chg_type = POWER_SUPPLY_TYPE_USB_DCP;
+ rphy->chg_state = USB_CHG_STATE_DETECTED;
+ delay = 0;
+ } else {
+ rphy->chg_type = POWER_SUPPLY_TYPE_USB;
+ rphy->chg_state = USB_CHG_STATE_DETECTED;
+ delay = 0;
+ }
+ }
+ break;
+ case USB_CHG_STATE_PRIMARY_DONE:
+ vout = property_enabled(rphy->grf,
+ &rphy->phy_cfg->chg_det.dcp_det);
+ /* Turn off voltage source */
+ rockchip_chg_enable_secondary_det(rphy, false);
+ if (vout)
+ rphy->chg_type = POWER_SUPPLY_TYPE_USB_DCP;
+ else
+ rphy->chg_type = POWER_SUPPLY_TYPE_USB_CDP;
+ /* fall through */
+ case USB_CHG_STATE_SECONDARY_DONE:
+ rphy->chg_state = USB_CHG_STATE_DETECTED;
+ delay = 0;
+ /* fall through */
+ case USB_CHG_STATE_DETECTED:
+ /* put the controller in normal mode */
+ property_enable(base, &rphy->phy_cfg->chg_det.opmode, true);
+ rockchip_usb2phy_otg_sm_work(&rport->otg_sm_work.work);
+ dev_info(&rport->phy->dev, "charger = %s\n",
+ chg_to_string(rphy->chg_type));
+ return;
+ default:
+ return;
+ }
+
+ schedule_delayed_work(&rport->chg_work, delay);
+}
+
+/*
+ * 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->grf, &rport->port_cfg->ls_det_clr, true);
+ property_enable(rphy->grf, &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->grf, &rport->port_cfg->ls_det_st))
+ return IRQ_NONE;
+
+ mutex_lock(&rport->mutex);
+
+ /* disable linestate detect irq and clear its status */
+ property_enable(rphy->grf, &rport->port_cfg->ls_det_en, false);
+ property_enable(rphy->grf, &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 irqreturn_t rockchip_usb2phy_bvalid_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->grf, &rport->port_cfg->bvalid_det_st))
+ return IRQ_NONE;
+
+ mutex_lock(&rport->mutex);
+
+ /* clear bvalid detect irq pending status */
+ property_enable(rphy->grf, &rport->port_cfg->bvalid_det_clr, true);
+
+ mutex_unlock(&rport->mutex);
+
+ rockchip_usb2phy_otg_sm_work(&rport->otg_sm_work.work);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t rockchip_usb2phy_otg_mux_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->grf, &rport->port_cfg->bvalid_det_st))
+ return rockchip_usb2phy_bvalid_irq(irq, data);
+ else
+ return IRQ_NONE;
+}
+
+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 linestate irq handle\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rockchip_otg_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct rockchip_usb2phy_port *rport =
+ container_of(nb, struct rockchip_usb2phy_port, event_nb);
+
+ schedule_delayed_work(&rport->otg_sm_work, OTG_SCHEDULE_DELAY);
+
+ return NOTIFY_DONE;
+}
+
+static int rockchip_usb2phy_otg_port_init(struct rockchip_usb2phy *rphy,
+ struct rockchip_usb2phy_port *rport,
+ struct device_node *child_np)
+{
+ int ret;
+
+ rport->port_id = USB2PHY_PORT_OTG;
+ rport->port_cfg = &rphy->phy_cfg->port_cfgs[USB2PHY_PORT_OTG];
+ rport->state = OTG_STATE_UNDEFINED;
+
+ /*
+ * set suspended flag to true, but actually don't
+ * put phy in suspend mode, it aims to enable usb
+ * phy and clock in power_on() called by usb controller
+ * driver during probe.
+ */
+ rport->suspended = true;
+ rport->vbus_attached = false;
+
+ mutex_init(&rport->mutex);
+
+ rport->mode = of_usb_get_dr_mode_by_phy(child_np, -1);
+ if (rport->mode == USB_DR_MODE_HOST ||
+ rport->mode == USB_DR_MODE_UNKNOWN) {
+ ret = 0;
+ goto out;
+ }
+
+ INIT_DELAYED_WORK(&rport->chg_work, rockchip_chg_detect_work);
+ INIT_DELAYED_WORK(&rport->otg_sm_work, rockchip_usb2phy_otg_sm_work);
+
+ rport->utmi_avalid =
+ of_property_read_bool(child_np, "rockchip,utmi-avalid");
+
+ /*
+ * Some SoCs use one interrupt with otg-id/otg-bvalid/linestate
+ * interrupts muxed together, so probe the otg-mux interrupt first,
+ * if not found, then look for the regular interrupts one by one.
+ */
+ rport->otg_mux_irq = of_irq_get_byname(child_np, "otg-mux");
+ if (rport->otg_mux_irq > 0) {
+ ret = devm_request_threaded_irq(rphy->dev, rport->otg_mux_irq,
+ NULL,
+ rockchip_usb2phy_otg_mux_irq,
+ IRQF_ONESHOT,
+ "rockchip_usb2phy_otg",
+ rport);
+ if (ret) {
+ dev_err(rphy->dev,
+ "failed to request otg-mux irq handle\n");
+ goto out;
+ }
+ } else {
+ rport->bvalid_irq = of_irq_get_byname(child_np, "otg-bvalid");
+ if (rport->bvalid_irq < 0) {
+ dev_err(rphy->dev, "no vbus valid irq provided\n");
+ ret = rport->bvalid_irq;
+ goto out;
+ }
+
+ ret = devm_request_threaded_irq(rphy->dev, rport->bvalid_irq,
+ NULL,
+ rockchip_usb2phy_bvalid_irq,
+ IRQF_ONESHOT,
+ "rockchip_usb2phy_bvalid",
+ rport);
+ if (ret) {
+ dev_err(rphy->dev,
+ "failed to request otg-bvalid irq handle\n");
+ goto out;
+ }
+ }
+
+ if (!IS_ERR(rphy->edev)) {
+ rport->event_nb.notifier_call = rockchip_otg_event;
+
+ ret = devm_extcon_register_notifier(rphy->dev, rphy->edev,
+ EXTCON_USB_HOST, &rport->event_nb);
+ if (ret)
+ dev_err(rphy->dev, "register USB HOST notifier failed\n");
+ }
+
+out:
+ return ret;
+}
+
+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_device_is_compatible(np, "rockchip,rv1108-usb2phy")) {
+ rphy->usbgrf =
+ syscon_regmap_lookup_by_phandle(dev->of_node,
+ "rockchip,usbgrf");
+ if (IS_ERR(rphy->usbgrf))
+ return PTR_ERR(rphy->usbgrf);
+ } else {
+ rphy->usbgrf = NULL;
+ }
+
+ if (of_property_read_u32(np, "reg", &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;
+ rphy->chg_state = USB_CHG_STATE_UNDEFINED;
+ rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN;
+ platform_set_drvdata(pdev, rphy);
+
+ ret = rockchip_usb2phy_extcon_register(rphy);
+ if (ret)
+ return ret;
+
+ /* 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 aims to support both otg-port and host-port */
+ if (of_node_cmp(child_np->name, "host-port") &&
+ of_node_cmp(child_np->name, "otg-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);
+
+ /* initialize otg/host port separately */
+ if (!of_node_cmp(child_np->name, "host-port")) {
+ ret = rockchip_usb2phy_host_port_init(rphy, rport,
+ child_np);
+ if (ret)
+ goto put_child;
+ } else {
+ ret = rockchip_usb2phy_otg_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 rk3228_phy_cfgs[] = {
+ {
+ .reg = 0x760,
+ .num_ports = 2,
+ .clkout_ctl = { 0x0768, 4, 4, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x0760, 15, 0, 0, 0x1d1 },
+ .bvalid_det_en = { 0x0680, 3, 3, 0, 1 },
+ .bvalid_det_st = { 0x0690, 3, 3, 0, 1 },
+ .bvalid_det_clr = { 0x06a0, 3, 3, 0, 1 },
+ .ls_det_en = { 0x0680, 2, 2, 0, 1 },
+ .ls_det_st = { 0x0690, 2, 2, 0, 1 },
+ .ls_det_clr = { 0x06a0, 2, 2, 0, 1 },
+ .utmi_bvalid = { 0x0480, 4, 4, 0, 1 },
+ .utmi_ls = { 0x0480, 3, 2, 0, 1 },
+ },
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0x0764, 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 }
+ }
+ },
+ .chg_det = {
+ .opmode = { 0x0760, 3, 0, 5, 1 },
+ .cp_det = { 0x0884, 4, 4, 0, 1 },
+ .dcp_det = { 0x0884, 3, 3, 0, 1 },
+ .dp_det = { 0x0884, 5, 5, 0, 1 },
+ .idm_sink_en = { 0x0768, 8, 8, 0, 1 },
+ .idp_sink_en = { 0x0768, 7, 7, 0, 1 },
+ .idp_src_en = { 0x0768, 9, 9, 0, 1 },
+ .rdm_pdwn_en = { 0x0768, 10, 10, 0, 1 },
+ .vdm_src_en = { 0x0768, 12, 12, 0, 1 },
+ .vdp_src_en = { 0x0768, 11, 11, 0, 1 },
+ },
+ },
+ {
+ .reg = 0x800,
+ .num_ports = 2,
+ .clkout_ctl = { 0x0808, 4, 4, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x800, 15, 0, 0, 0x1d1 },
+ .ls_det_en = { 0x0684, 0, 0, 0, 1 },
+ .ls_det_st = { 0x0694, 0, 0, 0, 1 },
+ .ls_det_clr = { 0x06a4, 0, 0, 0, 1 }
+ },
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0x804, 15, 0, 0, 0x1d1 },
+ .ls_det_en = { 0x0684, 1, 1, 0, 1 },
+ .ls_det_st = { 0x0694, 1, 1, 0, 1 },
+ .ls_det_clr = { 0x06a4, 1, 1, 0, 1 }
+ }
+ },
+ },
+ { /* sentinel */ }
+};
+
+static const struct rockchip_usb2phy_cfg rk3328_phy_cfgs[] = {
+ {
+ .reg = 0x100,
+ .num_ports = 2,
+ .clkout_ctl = { 0x108, 4, 4, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x0100, 15, 0, 0, 0x1d1 },
+ .bvalid_det_en = { 0x0110, 2, 2, 0, 1 },
+ .bvalid_det_st = { 0x0114, 2, 2, 0, 1 },
+ .bvalid_det_clr = { 0x0118, 2, 2, 0, 1 },
+ .ls_det_en = { 0x0110, 0, 0, 0, 1 },
+ .ls_det_st = { 0x0114, 0, 0, 0, 1 },
+ .ls_det_clr = { 0x0118, 0, 0, 0, 1 },
+ .utmi_avalid = { 0x0120, 10, 10, 0, 1 },
+ .utmi_bvalid = { 0x0120, 9, 9, 0, 1 },
+ .utmi_ls = { 0x0120, 5, 4, 0, 1 },
+ },
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0x104, 15, 0, 0, 0x1d1 },
+ .ls_det_en = { 0x110, 1, 1, 0, 1 },
+ .ls_det_st = { 0x114, 1, 1, 0, 1 },
+ .ls_det_clr = { 0x118, 1, 1, 0, 1 },
+ .utmi_ls = { 0x120, 17, 16, 0, 1 },
+ .utmi_hstdet = { 0x120, 19, 19, 0, 1 }
+ }
+ },
+ .chg_det = {
+ .opmode = { 0x0100, 3, 0, 5, 1 },
+ .cp_det = { 0x0120, 24, 24, 0, 1 },
+ .dcp_det = { 0x0120, 23, 23, 0, 1 },
+ .dp_det = { 0x0120, 25, 25, 0, 1 },
+ .idm_sink_en = { 0x0108, 8, 8, 0, 1 },
+ .idp_sink_en = { 0x0108, 7, 7, 0, 1 },
+ .idp_src_en = { 0x0108, 9, 9, 0, 1 },
+ .rdm_pdwn_en = { 0x0108, 10, 10, 0, 1 },
+ .vdm_src_en = { 0x0108, 12, 12, 0, 1 },
+ .vdp_src_en = { 0x0108, 11, 11, 0, 1 },
+ },
+ },
+ { /* sentinel */ }
+};
+
+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_OTG] = {
+ .phy_sus = { 0xe454, 1, 0, 2, 1 },
+ .bvalid_det_en = { 0xe3c0, 3, 3, 0, 1 },
+ .bvalid_det_st = { 0xe3e0, 3, 3, 0, 1 },
+ .bvalid_det_clr = { 0xe3d0, 3, 3, 0, 1 },
+ .utmi_avalid = { 0xe2ac, 7, 7, 0, 1 },
+ .utmi_bvalid = { 0xe2ac, 12, 12, 0, 1 },
+ },
+ [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 }
+ }
+ },
+ .chg_det = {
+ .opmode = { 0xe454, 3, 0, 5, 1 },
+ .cp_det = { 0xe2ac, 2, 2, 0, 1 },
+ .dcp_det = { 0xe2ac, 1, 1, 0, 1 },
+ .dp_det = { 0xe2ac, 0, 0, 0, 1 },
+ .idm_sink_en = { 0xe450, 8, 8, 0, 1 },
+ .idp_sink_en = { 0xe450, 7, 7, 0, 1 },
+ .idp_src_en = { 0xe450, 9, 9, 0, 1 },
+ .rdm_pdwn_en = { 0xe450, 10, 10, 0, 1 },
+ .vdm_src_en = { 0xe450, 12, 12, 0, 1 },
+ .vdp_src_en = { 0xe450, 11, 11, 0, 1 },
+ },
+ },
+ {
+ .reg = 0xe460,
+ .num_ports = 2,
+ .clkout_ctl = { 0xe460, 4, 4, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0xe464, 1, 0, 2, 1 },
+ .bvalid_det_en = { 0xe3c0, 8, 8, 0, 1 },
+ .bvalid_det_st = { 0xe3e0, 8, 8, 0, 1 },
+ .bvalid_det_clr = { 0xe3d0, 8, 8, 0, 1 },
+ .utmi_avalid = { 0xe2ac, 10, 10, 0, 1 },
+ .utmi_bvalid = { 0xe2ac, 16, 16, 0, 1 },
+ },
+ [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 rockchip_usb2phy_cfg rv1108_phy_cfgs[] = {
+ {
+ .reg = 0x100,
+ .num_ports = 2,
+ .clkout_ctl = { 0x108, 4, 4, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x0100, 15, 0, 0, 0x1d1 },
+ .bvalid_det_en = { 0x0680, 3, 3, 0, 1 },
+ .bvalid_det_st = { 0x0690, 3, 3, 0, 1 },
+ .bvalid_det_clr = { 0x06a0, 3, 3, 0, 1 },
+ .ls_det_en = { 0x0680, 2, 2, 0, 1 },
+ .ls_det_st = { 0x0690, 2, 2, 0, 1 },
+ .ls_det_clr = { 0x06a0, 2, 2, 0, 1 },
+ .utmi_bvalid = { 0x0804, 10, 10, 0, 1 },
+ .utmi_ls = { 0x0804, 13, 12, 0, 1 },
+ },
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0x0104, 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 = { 0x0804, 9, 8, 0, 1 },
+ .utmi_hstdet = { 0x0804, 7, 7, 0, 1 }
+ }
+ },
+ .chg_det = {
+ .opmode = { 0x0100, 3, 0, 5, 1 },
+ .cp_det = { 0x0804, 1, 1, 0, 1 },
+ .dcp_det = { 0x0804, 0, 0, 0, 1 },
+ .dp_det = { 0x0804, 2, 2, 0, 1 },
+ .idm_sink_en = { 0x0108, 8, 8, 0, 1 },
+ .idp_sink_en = { 0x0108, 7, 7, 0, 1 },
+ .idp_src_en = { 0x0108, 9, 9, 0, 1 },
+ .rdm_pdwn_en = { 0x0108, 10, 10, 0, 1 },
+ .vdm_src_en = { 0x0108, 12, 12, 0, 1 },
+ .vdp_src_en = { 0x0108, 11, 11, 0, 1 },
+ },
+ },
+ { /* sentinel */ }
+};
+
+static const struct of_device_id rockchip_usb2phy_dt_match[] = {
+ { .compatible = "rockchip,rk3228-usb2phy", .data = &rk3228_phy_cfgs },
+ { .compatible = "rockchip,rk3328-usb2phy", .data = &rk3328_phy_cfgs },
+ { .compatible = "rockchip,rk3366-usb2phy", .data = &rk3366_phy_cfgs },
+ { .compatible = "rockchip,rk3399-usb2phy", .data = &rk3399_phy_cfgs },
+ { .compatible = "rockchip,rv1108-usb2phy", .data = &rv1108_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/rockchip/phy-rockchip-pcie.c b/drivers/phy/rockchip/phy-rockchip-pcie.c
new file mode 100644
index 000000000..7cbdde029
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-pcie.c
@@ -0,0 +1,449 @@
+/*
+ * 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 phy_pcie_instance {
+ struct phy *phy;
+ u32 index;
+ } phys[PHY_MAX_LANE_NUM];
+ struct mutex pcie_mutex;
+ struct reset_control *phy_rst;
+ struct clk *clk_pciephy_ref;
+ int pwr_cnt;
+ int init_cnt;
+};
+
+static struct rockchip_pcie_phy *to_pcie_phy(struct phy_pcie_instance *inst)
+{
+ return container_of(inst, struct rockchip_pcie_phy,
+ phys[inst->index]);
+}
+
+static struct phy *rockchip_pcie_phy_of_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct rockchip_pcie_phy *rk_phy = dev_get_drvdata(dev);
+
+ if (args->args_count == 0)
+ return rk_phy->phys[0].phy;
+
+ if (WARN_ON(args->args[0] >= PHY_MAX_LANE_NUM))
+ return ERR_PTR(-ENODEV);
+
+ return rk_phy->phys[args->args[0]].phy;
+}
+
+
+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 phy_pcie_instance *inst = phy_get_drvdata(phy);
+ struct rockchip_pcie_phy *rk_phy = to_pcie_phy(inst);
+ int err = 0;
+
+ mutex_lock(&rk_phy->pcie_mutex);
+
+ regmap_write(rk_phy->reg_base,
+ rk_phy->phy_data->pcie_laneoff,
+ HIWORD_UPDATE(PHY_LANE_IDLE_OFF,
+ PHY_LANE_IDLE_MASK,
+ PHY_LANE_IDLE_A_SHIFT + inst->index));
+
+ if (--rk_phy->pwr_cnt)
+ goto err_out;
+
+ err = reset_control_assert(rk_phy->phy_rst);
+ if (err) {
+ dev_err(&phy->dev, "assert phy_rst err %d\n", err);
+ goto err_restore;
+ }
+
+err_out:
+ mutex_unlock(&rk_phy->pcie_mutex);
+ return 0;
+
+err_restore:
+ rk_phy->pwr_cnt++;
+ regmap_write(rk_phy->reg_base,
+ rk_phy->phy_data->pcie_laneoff,
+ HIWORD_UPDATE(!PHY_LANE_IDLE_OFF,
+ PHY_LANE_IDLE_MASK,
+ PHY_LANE_IDLE_A_SHIFT + inst->index));
+ mutex_unlock(&rk_phy->pcie_mutex);
+ return err;
+}
+
+static int rockchip_pcie_phy_power_on(struct phy *phy)
+{
+ struct phy_pcie_instance *inst = phy_get_drvdata(phy);
+ struct rockchip_pcie_phy *rk_phy = to_pcie_phy(inst);
+ int err = 0;
+ u32 status;
+ unsigned long timeout;
+
+ mutex_lock(&rk_phy->pcie_mutex);
+
+ if (rk_phy->pwr_cnt++)
+ goto err_out;
+
+ err = reset_control_deassert(rk_phy->phy_rst);
+ if (err) {
+ dev_err(&phy->dev, "deassert phy_rst err %d\n", err);
+ goto err_pwr_cnt;
+ }
+
+ 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));
+
+ regmap_write(rk_phy->reg_base,
+ rk_phy->phy_data->pcie_laneoff,
+ HIWORD_UPDATE(!PHY_LANE_IDLE_OFF,
+ PHY_LANE_IDLE_MASK,
+ PHY_LANE_IDLE_A_SHIFT + inst->index));
+
+ /*
+ * 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;
+ }
+
+err_out:
+ mutex_unlock(&rk_phy->pcie_mutex);
+ return 0;
+
+err_pll_lock:
+ reset_control_assert(rk_phy->phy_rst);
+err_pwr_cnt:
+ rk_phy->pwr_cnt--;
+ mutex_unlock(&rk_phy->pcie_mutex);
+ return err;
+}
+
+static int rockchip_pcie_phy_init(struct phy *phy)
+{
+ struct phy_pcie_instance *inst = phy_get_drvdata(phy);
+ struct rockchip_pcie_phy *rk_phy = to_pcie_phy(inst);
+ int err = 0;
+
+ mutex_lock(&rk_phy->pcie_mutex);
+
+ if (rk_phy->init_cnt++)
+ goto err_out;
+
+ 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;
+ }
+
+err_out:
+ mutex_unlock(&rk_phy->pcie_mutex);
+ return 0;
+
+err_reset:
+
+ clk_disable_unprepare(rk_phy->clk_pciephy_ref);
+err_refclk:
+ rk_phy->init_cnt--;
+ mutex_unlock(&rk_phy->pcie_mutex);
+ return err;
+}
+
+static int rockchip_pcie_phy_exit(struct phy *phy)
+{
+ struct phy_pcie_instance *inst = phy_get_drvdata(phy);
+ struct rockchip_pcie_phy *rk_phy = to_pcie_phy(inst);
+
+ mutex_lock(&rk_phy->pcie_mutex);
+
+ if (--rk_phy->init_cnt)
+ goto err_init_cnt;
+
+ clk_disable_unprepare(rk_phy->clk_pciephy_ref);
+
+err_init_cnt:
+ mutex_unlock(&rk_phy->pcie_mutex);
+ return 0;
+}
+
+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_provider *phy_provider;
+ struct regmap *grf;
+ const struct of_device_id *of_id;
+ int i;
+ u32 phy_num;
+
+ 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;
+
+ mutex_init(&rk_phy->pcie_mutex);
+
+ 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);
+ }
+
+ /* parse #phy-cells to see if it's legacy PHY model */
+ if (of_property_read_u32(dev->of_node, "#phy-cells", &phy_num))
+ return -ENOENT;
+
+ phy_num = (phy_num == 0) ? 1 : PHY_MAX_LANE_NUM;
+ dev_dbg(dev, "phy number is %d\n", phy_num);
+
+ for (i = 0; i < phy_num; i++) {
+ rk_phy->phys[i].phy = devm_phy_create(dev, dev->of_node, &ops);
+ if (IS_ERR(rk_phy->phys[i].phy)) {
+ dev_err(dev, "failed to create PHY%d\n", i);
+ return PTR_ERR(rk_phy->phys[i].phy);
+ }
+ rk_phy->phys[i].index = i;
+ phy_set_drvdata(rk_phy->phys[i].phy, &rk_phy->phys[i]);
+ }
+
+ platform_set_drvdata(pdev, rk_phy);
+ phy_provider = devm_of_phy_provider_register(dev,
+ rockchip_pcie_phy_of_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/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c
new file mode 100644
index 000000000..76a4b58ec
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-typec.c
@@ -0,0 +1,1239 @@
+/*
+ * 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_CTRL (0xf0 << 2)
+#define CMN_TXPDCAL_OVRD (0xf1 << 2)
+
+/* For CMN_TXPUCAL_CTRL, CMN_TXPDCAL_CTRL */
+#define CMN_TXPXCAL_START BIT(15)
+#define CMN_TXPXCAL_DONE BIT(14)
+#define CMN_TXPXCAL_NO_RESPONSE BIT(13)
+#define CMN_TXPXCAL_CURRENT_RESPONSE BIT(12)
+
+#define CMN_TXPU_ADJ_CTRL (0x108 << 2)
+#define CMN_TXPD_ADJ_CTRL (0x10c << 2)
+
+/*
+ * For CMN_TXPUCAL_CTRL, CMN_TXPDCAL_CTRL,
+ * CMN_TXPU_ADJ_CTRL, CMN_TXPDCAL_CTRL
+ *
+ * NOTE: some of these registers are documented to be 2's complement
+ * signed numbers, but then documented to be always positive. Weird.
+ * In such a case, using CMN_CALIB_CODE_POS() avoids the unnecessary
+ * sign extension.
+ */
+#define CMN_CALIB_CODE_WIDTH 7
+#define CMN_CALIB_CODE_OFFSET 0
+#define CMN_CALIB_CODE_MASK GENMASK(CMN_CALIB_CODE_WIDTH, 0)
+#define CMN_CALIB_CODE(x) \
+ sign_extend32((x) >> CMN_CALIB_CODE_OFFSET, CMN_CALIB_CODE_WIDTH)
+
+#define CMN_CALIB_CODE_POS_MASK GENMASK(CMN_CALIB_CODE_WIDTH - 1, 0)
+#define CMN_CALIB_CODE_POS(x) \
+ (((x) >> CMN_CALIB_CODE_OFFSET) & CMN_CALIB_CODE_POS_MASK)
+
+#define CMN_DIAG_PLL0_FBH_OVRD (0x1c0 << 2)
+#define CMN_DIAG_PLL0_FBL_OVRD (0x1c1 << 2)
+#define CMN_DIAG_PLL0_OVRD (0x1c2 << 2)
+#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 TX_TXCC_MGNLS_MULT_000(n) ((0x4058 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNLS_MULT_001(n) ((0x4059 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNLS_MULT_010(n) ((0x405a | ((n) << 9)) << 2)
+#define TX_TXCC_MGNLS_MULT_011(n) ((0x405b | ((n) << 9)) << 2)
+#define TX_TXCC_MGNLS_MULT_100(n) ((0x405c | ((n) << 9)) << 2)
+#define TX_TXCC_MGNLS_MULT_101(n) ((0x405d | ((n) << 9)) << 2)
+#define TX_TXCC_MGNLS_MULT_110(n) ((0x405e | ((n) << 9)) << 2)
+#define TX_TXCC_MGNLS_MULT_111(n) ((0x405f | ((n) << 9)) << 2)
+
+#define XCVR_DIAG_PLLDRC_CTRL(n) ((0x40e0 | ((n) << 9)) << 2)
+#define XCVR_DIAG_BIDI_CTRL(n) ((0x40e8 | ((n) << 9)) << 2)
+#define XCVR_DIAG_LANE_FCM_EN_MGN(n) ((0x40f2 | ((n) << 9)) << 2)
+#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)
+
+/* Use this for "n" in macros like "_MULT_XXX" to target the aux channel */
+#define AUX_CH_LANE 8
+
+#define TX_ANA_CTRL_REG_1 (0x5020 << 2)
+
+#define TXDA_DP_AUX_EN BIT(15)
+#define AUXDA_SE_EN BIT(14)
+#define TXDA_CAL_LATCH_EN BIT(13)
+#define AUXDA_POLARITY BIT(12)
+#define TXDA_DRV_POWER_ISOLATION_EN BIT(11)
+#define TXDA_DRV_POWER_EN_PH_2_N BIT(10)
+#define TXDA_DRV_POWER_EN_PH_1_N BIT(9)
+#define TXDA_BGREF_EN BIT(8)
+#define TXDA_DRV_LDO_EN BIT(7)
+#define TXDA_DECAP_EN_DEL BIT(6)
+#define TXDA_DECAP_EN BIT(5)
+#define TXDA_UPHY_SUPPLY_EN_DEL BIT(4)
+#define TXDA_UPHY_SUPPLY_EN BIT(3)
+#define TXDA_LOW_LEAKAGE_EN BIT(2)
+#define TXDA_DRV_IDLE_LOWI_EN BIT(1)
+#define TXDA_DRV_CMN_MODE_EN BIT(0)
+
+#define TX_ANA_CTRL_REG_2 (0x5021 << 2)
+
+#define AUXDA_DEBOUNCING_CLK BIT(15)
+#define TXDA_LPBK_RECOVERED_CLK_EN BIT(14)
+#define TXDA_LPBK_ISI_GEN_EN BIT(13)
+#define TXDA_LPBK_SERIAL_EN BIT(12)
+#define TXDA_LPBK_LINE_EN BIT(11)
+#define TXDA_DRV_LDO_REDC_SINKIQ BIT(10)
+#define XCVR_DECAP_EN_DEL BIT(9)
+#define XCVR_DECAP_EN BIT(8)
+#define TXDA_MPHY_ENABLE_HS_NT BIT(7)
+#define TXDA_MPHY_SA_MODE BIT(6)
+#define TXDA_DRV_LDO_RBYR_FB_EN BIT(5)
+#define TXDA_DRV_RST_PULL_DOWN BIT(4)
+#define TXDA_DRV_LDO_BG_FB_EN BIT(3)
+#define TXDA_DRV_LDO_BG_REF_EN BIT(2)
+#define TXDA_DRV_PREDRV_EN_DEL BIT(1)
+#define TXDA_DRV_PREDRV_EN BIT(0)
+
+#define TXDA_COEFF_CALC_CTRL (0x5022 << 2)
+
+#define TX_HIGH_Z BIT(6)
+#define TX_VMARGIN_OFFSET 3
+#define TX_VMARGIN_MASK 0x7
+#define LOW_POWER_SWING_EN BIT(2)
+#define TX_FCM_DRV_MAIN_EN BIT(1)
+#define TX_FCM_FULL_MARGIN BIT(0)
+
+#define TX_DIG_CTRL_REG_2 (0x5024 << 2)
+
+#define TX_HIGH_Z_TM_EN BIT(15)
+#define TX_RESCAL_CODE_OFFSET 0
+#define TX_RESCAL_CODE_MASK 0x3f
+
+#define TXDA_CYA_AUXDA_CYA (0x5025 << 2)
+#define TX_ANA_CTRL_REG_3 (0x5026 << 2)
+#define TX_ANA_CTRL_REG_4 (0x5027 << 2)
+#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: usb3-phy port configuration.
+ * @reg: the base address for usb3-phy config.
+ * @typec_conn_dir: the register of type-c connector direction.
+ * @usb3tousb2_en: the register of type-c force usb2 to usb2 enable.
+ * @external_psm: the register of type-c phy external psm clock.
+ * @pipe_status: the register of type-c phy pipe status.
+ * @usb3_host_disable: the register of type-c usb3 host disable.
+ * @usb3_host_port: the register of type-c usb3 host port.
+ * @uphy_dp_sel: the register of type-c phy DP select control.
+ */
+struct rockchip_usb3phy_port_cfg {
+ unsigned int reg;
+ struct usb3phy_reg typec_conn_dir;
+ struct usb3phy_reg usb3tousb2_en;
+ struct usb3phy_reg external_psm;
+ struct usb3phy_reg pipe_status;
+ struct usb3phy_reg usb3_host_disable;
+ struct usb3phy_reg usb3_host_port;
+ struct usb3phy_reg uphy_dp_sel;
+};
+
+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;
+ const 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 const struct rockchip_usb3phy_port_cfg rk3399_usb3phy_port_cfgs[] = {
+ {
+ .reg = 0xff7c0000,
+ .typec_conn_dir = { 0xe580, 0, 16 },
+ .usb3tousb2_en = { 0xe580, 3, 19 },
+ .external_psm = { 0xe588, 14, 30 },
+ .pipe_status = { 0xe5c0, 0, 0 },
+ .usb3_host_disable = { 0x2434, 0, 16 },
+ .usb3_host_port = { 0x2434, 12, 28 },
+ .uphy_dp_sel = { 0x6268, 19, 19 },
+ },
+ {
+ .reg = 0xff800000,
+ .typec_conn_dir = { 0xe58c, 0, 16 },
+ .usb3tousb2_en = { 0xe58c, 3, 19 },
+ .external_psm = { 0xe594, 14, 30 },
+ .pipe_status = { 0xe5c0, 16, 16 },
+ .usb3_host_disable = { 0x2444, 0, 16 },
+ .usb3_host_port = { 0x2444, 12, 28 },
+ .uphy_dp_sel = { 0x6268, 3, 19 },
+ },
+ { /* sentinel */ }
+};
+
+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_set_flip(struct rockchip_typec_phy *tcphy)
+{
+ u16 tx_ana_ctrl_reg_1;
+
+ /*
+ * Select the polarity of the xcvr:
+ * 1, Reverses the polarity (If TYPEC, Pulls ups aux_p and pull
+ * down aux_m)
+ * 0, Normal polarity (if TYPEC, pulls up aux_m and pulls down
+ * aux_p)
+ */
+ tx_ana_ctrl_reg_1 = readl(tcphy->base + TX_ANA_CTRL_REG_1);
+ if (!tcphy->flip)
+ tx_ana_ctrl_reg_1 |= AUXDA_POLARITY;
+ else
+ tx_ana_ctrl_reg_1 &= ~AUXDA_POLARITY;
+ writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1);
+}
+
+static void tcphy_dp_aux_calibration(struct rockchip_typec_phy *tcphy)
+{
+ u16 val;
+ u16 tx_ana_ctrl_reg_1;
+ u16 tx_ana_ctrl_reg_2;
+ s32 pu_calib_code, pd_calib_code;
+ s32 pu_adj, pd_adj;
+ u16 calib;
+
+ /*
+ * Calculate calibration code as per docs: use an average of the
+ * pull down and pull up. Then add in adjustments.
+ */
+ val = readl(tcphy->base + CMN_TXPUCAL_CTRL);
+ pu_calib_code = CMN_CALIB_CODE_POS(val);
+ val = readl(tcphy->base + CMN_TXPDCAL_CTRL);
+ pd_calib_code = CMN_CALIB_CODE_POS(val);
+ val = readl(tcphy->base + CMN_TXPU_ADJ_CTRL);
+ pu_adj = CMN_CALIB_CODE(val);
+ val = readl(tcphy->base + CMN_TXPD_ADJ_CTRL);
+ pd_adj = CMN_CALIB_CODE(val);
+ calib = (pu_calib_code + pd_calib_code) / 2 + pu_adj + pd_adj;
+
+ /* disable txda_cal_latch_en for rewrite the calibration values */
+ tx_ana_ctrl_reg_1 = readl(tcphy->base + TX_ANA_CTRL_REG_1);
+ tx_ana_ctrl_reg_1 &= ~TXDA_CAL_LATCH_EN;
+ writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1);
+
+ /* write the calibration, then delay 10 ms as sample in docs */
+ val = readl(tcphy->base + TX_DIG_CTRL_REG_2);
+ val &= ~(TX_RESCAL_CODE_MASK << TX_RESCAL_CODE_OFFSET);
+ val |= calib << TX_RESCAL_CODE_OFFSET;
+ writel(val, tcphy->base + TX_DIG_CTRL_REG_2);
+ usleep_range(10000, 10050);
+
+ /*
+ * Enable signal for latch that sample and holds calibration values.
+ * Activate this signal for 1 clock cycle to sample new calibration
+ * values.
+ */
+ tx_ana_ctrl_reg_1 |= TXDA_CAL_LATCH_EN;
+ writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1);
+ usleep_range(150, 200);
+
+ /* set TX Voltage Level and TX Deemphasis to 0 */
+ writel(0, tcphy->base + PHY_DP_TX_CTL);
+
+ /* re-enable decap */
+ tx_ana_ctrl_reg_2 = XCVR_DECAP_EN;
+ writel(tx_ana_ctrl_reg_2, tcphy->base + TX_ANA_CTRL_REG_2);
+ udelay(1);
+ tx_ana_ctrl_reg_2 |= XCVR_DECAP_EN_DEL;
+ writel(tx_ana_ctrl_reg_2, tcphy->base + TX_ANA_CTRL_REG_2);
+
+ writel(0, tcphy->base + TX_ANA_CTRL_REG_3);
+
+ tx_ana_ctrl_reg_1 |= TXDA_UPHY_SUPPLY_EN;
+ writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1);
+ udelay(1);
+ tx_ana_ctrl_reg_1 |= TXDA_UPHY_SUPPLY_EN_DEL;
+ writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1);
+
+ writel(0, tcphy->base + TX_ANA_CTRL_REG_5);
+
+ /*
+ * 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 */
+ tx_ana_ctrl_reg_1 |= TXDA_DRV_LDO_EN;
+ writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1);
+ udelay(5);
+ tx_ana_ctrl_reg_1 |= TXDA_BGREF_EN;
+ writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1);
+
+ /*
+ * re-enables the transmitter pre-driver, driver data selection MUX,
+ * and receiver detect circuits.
+ */
+ tx_ana_ctrl_reg_2 |= TXDA_DRV_PREDRV_EN;
+ writel(tx_ana_ctrl_reg_2, tcphy->base + TX_ANA_CTRL_REG_2);
+ udelay(1);
+ tx_ana_ctrl_reg_2 |= TXDA_DRV_PREDRV_EN_DEL;
+ writel(tx_ana_ctrl_reg_2, tcphy->base + TX_ANA_CTRL_REG_2);
+
+ /*
+ * Do all the undocumented magic:
+ * - Turn on TXDA_DP_AUX_EN, whatever that is, even though sample
+ * never shows this going on.
+ * - Turn on TXDA_DECAP_EN (and TXDA_DECAP_EN_DEL) even though
+ * docs say for aux it's always 0.
+ * - Turn off the LDO and BGREF, which we just spent time turning
+ * on above (???).
+ *
+ * Without this magic, things seem worse.
+ */
+ tx_ana_ctrl_reg_1 |= TXDA_DP_AUX_EN;
+ tx_ana_ctrl_reg_1 |= TXDA_DECAP_EN;
+ tx_ana_ctrl_reg_1 &= ~TXDA_DRV_LDO_EN;
+ tx_ana_ctrl_reg_1 &= ~TXDA_BGREF_EN;
+ writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1);
+ udelay(1);
+ tx_ana_ctrl_reg_1 |= TXDA_DECAP_EN_DEL;
+ writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1);
+
+ /*
+ * Undo the work we did to set the LDO voltage.
+ * This doesn't seem to help nor hurt, but it kinda goes with the
+ * undocumented magic above.
+ */
+ writel(0, tcphy->base + TX_ANA_CTRL_REG_4);
+
+ /* Don't set voltage swing to 400 mV peak to peak (differential) */
+ writel(0, tcphy->base + TXDA_COEFF_CALC_CTRL);
+
+ /* Init TXDA_CYA_AUXDA_CYA for unknown magic reasons */
+ writel(0, tcphy->base + TXDA_CYA_AUXDA_CYA);
+
+ /*
+ * More undocumented magic, presumably the goal of which is to
+ * make the "auxda_source_aux_oen" be ignored and instead to decide
+ * about "high impedance state" based on what software puts in the
+ * register TXDA_COEFF_CALC_CTRL (see TX_HIGH_Z). Since we only
+ * program that register once and we don't set the bit TX_HIGH_Z,
+ * presumably the goal here is that we should never put the analog
+ * driver in high impedance state.
+ */
+ val = readl(tcphy->base + TX_DIG_CTRL_REG_2);
+ val |= TX_HIGH_Z_TM_EN;
+ writel(val, tcphy->base + TX_DIG_CTRL_REG_2);
+}
+
+static int tcphy_phy_init(struct rockchip_typec_phy *tcphy, u8 mode)
+{
+ const 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_dp_aux_set_flip(tcphy);
+
+ 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 ufp, dp;
+ u8 mode;
+ int ret;
+
+ if (!edev)
+ return MODE_DFP_USB;
+
+ ufp = extcon_get_state(edev, EXTCON_USB);
+ 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 tcphy_cfg_usb3_to_usb2_only(struct rockchip_typec_phy *tcphy,
+ bool value)
+{
+ const struct rockchip_usb3phy_port_cfg *cfg = tcphy->port_cfgs;
+
+ property_enable(tcphy, &cfg->usb3tousb2_en, value);
+ property_enable(tcphy, &cfg->usb3_host_disable, value);
+ property_enable(tcphy, &cfg->usb3_host_port, !value);
+
+ return 0;
+}
+
+static int rockchip_usb3_phy_power_on(struct phy *phy)
+{
+ struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy);
+ const 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))) {
+ tcphy_cfg_usb3_to_usb2_only(tcphy, true);
+ goto unlock_ret;
+ }
+
+ if (tcphy->mode == new_mode)
+ goto unlock_ret;
+
+ if (tcphy->mode == MODE_DISCONNECT) {
+ ret = tcphy_phy_init(tcphy, new_mode);
+ if (ret)
+ goto unlock_ret;
+ }
+
+ /* 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);
+
+ /* enable usb3 host */
+ tcphy_cfg_usb3_to_usb2_only(tcphy, false);
+ 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);
+ tcphy_cfg_usb3_to_usb2_only(tcphy, false);
+
+ 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);
+ const struct rockchip_usb3phy_port_cfg *cfg = tcphy->port_cfgs;
+ 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);
+ ret = tcphy_phy_init(tcphy, new_mode);
+ } else if (tcphy->mode == MODE_DISCONNECT) {
+ ret = tcphy_phy_init(tcphy, new_mode);
+ }
+ if (ret)
+ goto unlock_ret;
+
+ property_enable(tcphy, &cfg->uphy_dp_sel, 1);
+
+ 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_parse_dt(struct rockchip_typec_phy *tcphy,
+ struct device *dev)
+{
+ 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)
+{
+ const 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;
+ const struct rockchip_usb3phy_port_cfg *phy_cfgs;
+ const struct of_device_id *match;
+ int index, ret;
+
+ tcphy = devm_kzalloc(dev, sizeof(*tcphy), GFP_KERNEL);
+ if (!tcphy)
+ 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;
+ }
+
+ 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);
+
+ phy_cfgs = match->data;
+ /* find out a proper config which can be matched with dt. */
+ index = 0;
+ while (phy_cfgs[index].reg) {
+ if (phy_cfgs[index].reg == res->start) {
+ tcphy->port_cfgs = &phy_cfgs[index];
+ break;
+ }
+
+ ++index;
+ }
+
+ if (!tcphy->port_cfgs) {
+ dev_err(dev, "no phy-config can be matched with %s node\n",
+ np->name);
+ return -EINVAL;
+ }
+
+ 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) == -ENODEV) {
+ tcphy->extcon = NULL;
+ } else {
+ 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);
+ pm_runtime_disable(dev);
+ 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");
+ pm_runtime_disable(dev);
+ 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",
+ .data = &rk3399_usb3phy_port_cfgs
+ },
+ { /* sentinel */ }
+};
+
+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/rockchip/phy-rockchip-usb.c b/drivers/phy/rockchip/phy-rockchip-usb.c
new file mode 100644
index 000000000..3378eeb7a
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-usb.c
@@ -0,0 +1,540 @@
+/*
+ * Rockchip usb PHY driver
+ *
+ * Copyright (C) 2014 Yunzhi Li <lyz@rock-chips.com>
+ * Copyright (C) 2014 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/clk-provider.h>
+#include <linux/io.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/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/delay.h>
+
+static int enable_usb_uart;
+
+#define HIWORD_UPDATE(val, mask) \
+ ((val) | (mask) << 16)
+
+#define UOC_CON0_SIDDQ BIT(13)
+
+struct rockchip_usb_phys {
+ int reg;
+ const char *pll_name;
+};
+
+struct rockchip_usb_phy_base;
+struct rockchip_usb_phy_pdata {
+ struct rockchip_usb_phys *phys;
+ int (*init_usb_uart)(struct regmap *grf);
+ int usb_uart_phy;
+};
+
+struct rockchip_usb_phy_base {
+ struct device *dev;
+ struct regmap *reg_base;
+ const struct rockchip_usb_phy_pdata *pdata;
+};
+
+struct rockchip_usb_phy {
+ struct rockchip_usb_phy_base *base;
+ struct device_node *np;
+ unsigned int reg_offset;
+ struct clk *clk;
+ struct clk *clk480m;
+ struct clk_hw clk480m_hw;
+ struct phy *phy;
+ bool uart_enabled;
+ struct reset_control *reset;
+ struct regulator *vbus;
+};
+
+static int rockchip_usb_phy_power(struct rockchip_usb_phy *phy,
+ bool siddq)
+{
+ u32 val = HIWORD_UPDATE(siddq ? UOC_CON0_SIDDQ : 0, UOC_CON0_SIDDQ);
+
+ return regmap_write(phy->base->reg_base, phy->reg_offset, val);
+}
+
+static unsigned long rockchip_usb_phy480m_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ return 480000000;
+}
+
+static void rockchip_usb_phy480m_disable(struct clk_hw *hw)
+{
+ struct rockchip_usb_phy *phy = container_of(hw,
+ struct rockchip_usb_phy,
+ clk480m_hw);
+
+ if (phy->vbus)
+ regulator_disable(phy->vbus);
+
+ /* Power down usb phy analog blocks by set siddq 1 */
+ rockchip_usb_phy_power(phy, 1);
+}
+
+static int rockchip_usb_phy480m_enable(struct clk_hw *hw)
+{
+ struct rockchip_usb_phy *phy = container_of(hw,
+ struct rockchip_usb_phy,
+ clk480m_hw);
+
+ /* Power up usb phy analog blocks by set siddq 0 */
+ return rockchip_usb_phy_power(phy, 0);
+}
+
+static int rockchip_usb_phy480m_is_enabled(struct clk_hw *hw)
+{
+ struct rockchip_usb_phy *phy = container_of(hw,
+ struct rockchip_usb_phy,
+ clk480m_hw);
+ int ret;
+ u32 val;
+
+ ret = regmap_read(phy->base->reg_base, phy->reg_offset, &val);
+ if (ret < 0)
+ return ret;
+
+ return (val & UOC_CON0_SIDDQ) ? 0 : 1;
+}
+
+static const struct clk_ops rockchip_usb_phy480m_ops = {
+ .enable = rockchip_usb_phy480m_enable,
+ .disable = rockchip_usb_phy480m_disable,
+ .is_enabled = rockchip_usb_phy480m_is_enabled,
+ .recalc_rate = rockchip_usb_phy480m_recalc_rate,
+};
+
+static int rockchip_usb_phy_power_off(struct phy *_phy)
+{
+ struct rockchip_usb_phy *phy = phy_get_drvdata(_phy);
+
+ if (phy->uart_enabled)
+ return -EBUSY;
+
+ clk_disable_unprepare(phy->clk480m);
+
+ return 0;
+}
+
+static int rockchip_usb_phy_power_on(struct phy *_phy)
+{
+ struct rockchip_usb_phy *phy = phy_get_drvdata(_phy);
+
+ if (phy->uart_enabled)
+ return -EBUSY;
+
+ if (phy->vbus) {
+ int ret;
+
+ ret = regulator_enable(phy->vbus);
+ if (ret)
+ return ret;
+ }
+
+ 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,
+};
+
+static void rockchip_usb_phy_action(void *data)
+{
+ struct rockchip_usb_phy *rk_phy = data;
+
+ if (!rk_phy->uart_enabled) {
+ of_clk_del_provider(rk_phy->np);
+ clk_unregister(rk_phy->clk480m);
+ }
+
+ if (rk_phy->clk)
+ clk_put(rk_phy->clk);
+}
+
+static int rockchip_usb_phy_init(struct rockchip_usb_phy_base *base,
+ struct device_node *child)
+{
+ struct rockchip_usb_phy *rk_phy;
+ unsigned int reg_offset;
+ const char *clk_name;
+ struct clk_init_data init;
+ int err, i;
+
+ rk_phy = devm_kzalloc(base->dev, sizeof(*rk_phy), GFP_KERNEL);
+ if (!rk_phy)
+ return -ENOMEM;
+
+ rk_phy->base = base;
+ rk_phy->np = child;
+
+ if (of_property_read_u32(child, "reg", &reg_offset)) {
+ dev_err(base->dev, "missing reg property in node %s\n",
+ child->name);
+ 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");
+ if (IS_ERR(rk_phy->clk))
+ rk_phy->clk = NULL;
+
+ i = 0;
+ init.name = NULL;
+ while (base->pdata->phys[i].reg) {
+ if (base->pdata->phys[i].reg == reg_offset) {
+ init.name = base->pdata->phys[i].pll_name;
+ break;
+ }
+ i++;
+ }
+
+ if (!init.name) {
+ dev_err(base->dev, "phy data not found\n");
+ return -EINVAL;
+ }
+
+ if (enable_usb_uart && base->pdata->usb_uart_phy == i) {
+ dev_dbg(base->dev, "phy%d used as uart output\n", i);
+ rk_phy->uart_enabled = true;
+ } else {
+ if (rk_phy->clk) {
+ clk_name = __clk_get_name(rk_phy->clk);
+ init.flags = 0;
+ init.parent_names = &clk_name;
+ init.num_parents = 1;
+ } else {
+ init.flags = 0;
+ init.parent_names = NULL;
+ init.num_parents = 0;
+ }
+
+ init.ops = &rockchip_usb_phy480m_ops;
+ rk_phy->clk480m_hw.init = &init;
+
+ rk_phy->clk480m = clk_register(base->dev, &rk_phy->clk480m_hw);
+ if (IS_ERR(rk_phy->clk480m)) {
+ err = PTR_ERR(rk_phy->clk480m);
+ goto err_clk;
+ }
+
+ err = of_clk_add_provider(child, of_clk_src_simple_get,
+ rk_phy->clk480m);
+ if (err < 0)
+ goto err_clk_prov;
+ }
+
+ err = devm_add_action_or_reset(base->dev, rockchip_usb_phy_action,
+ rk_phy);
+ if (err)
+ return err;
+
+ rk_phy->phy = devm_phy_create(base->dev, child, &ops);
+ if (IS_ERR(rk_phy->phy)) {
+ dev_err(base->dev, "failed to create PHY\n");
+ return PTR_ERR(rk_phy->phy);
+ }
+ phy_set_drvdata(rk_phy->phy, rk_phy);
+
+ rk_phy->vbus = devm_regulator_get_optional(&rk_phy->phy->dev, "vbus");
+ if (IS_ERR(rk_phy->vbus)) {
+ if (PTR_ERR(rk_phy->vbus) == -EPROBE_DEFER)
+ return PTR_ERR(rk_phy->vbus);
+ rk_phy->vbus = NULL;
+ }
+
+ /*
+ * When acting as uart-pipe, just keep clock on otherwise
+ * only power up usb phy when it use, so disable it when init
+ */
+ if (rk_phy->uart_enabled)
+ return clk_prepare_enable(rk_phy->clk);
+ else
+ return rockchip_usb_phy_power(rk_phy, 1);
+
+err_clk_prov:
+ if (!rk_phy->uart_enabled)
+ clk_unregister(rk_phy->clk480m);
+err_clk:
+ if (rk_phy->clk)
+ clk_put(rk_phy->clk);
+ return err;
+}
+
+static const struct rockchip_usb_phy_pdata rk3066a_pdata = {
+ .phys = (struct rockchip_usb_phys[]){
+ { .reg = 0x17c, .pll_name = "sclk_otgphy0_480m" },
+ { .reg = 0x188, .pll_name = "sclk_otgphy1_480m" },
+ { /* sentinel */ }
+ },
+};
+
+static const struct rockchip_usb_phy_pdata rk3188_pdata = {
+ .phys = (struct rockchip_usb_phys[]){
+ { .reg = 0x10c, .pll_name = "sclk_otgphy0_480m" },
+ { .reg = 0x11c, .pll_name = "sclk_otgphy1_480m" },
+ { /* sentinel */ }
+ },
+};
+
+#define RK3288_UOC0_CON0 0x320
+#define RK3288_UOC0_CON0_COMMON_ON_N BIT(0)
+#define RK3288_UOC0_CON0_DISABLE BIT(4)
+
+#define RK3288_UOC0_CON2 0x328
+#define RK3288_UOC0_CON2_SOFT_CON_SEL BIT(2)
+
+#define RK3288_UOC0_CON3 0x32c
+#define RK3288_UOC0_CON3_UTMI_SUSPENDN BIT(0)
+#define RK3288_UOC0_CON3_UTMI_OPMODE_NODRIVING (1 << 1)
+#define RK3288_UOC0_CON3_UTMI_OPMODE_MASK (3 << 1)
+#define RK3288_UOC0_CON3_UTMI_XCVRSEELCT_FSTRANSC (1 << 3)
+#define RK3288_UOC0_CON3_UTMI_XCVRSEELCT_MASK (3 << 3)
+#define RK3288_UOC0_CON3_UTMI_TERMSEL_FULLSPEED BIT(5)
+#define RK3288_UOC0_CON3_BYPASSDMEN BIT(6)
+#define RK3288_UOC0_CON3_BYPASSSEL BIT(7)
+
+/*
+ * Enable the bypass of uart2 data through the otg usb phy.
+ * Original description in the TRM.
+ * 1. Disable the OTG block by setting OTGDISABLE0 to 1’b1.
+ * 2. Disable the pull-up resistance on the D+ line by setting
+ * OPMODE0[1:0] to 2’b01.
+ * 3. To ensure that the XO, Bias, and PLL blocks are powered down in Suspend
+ * mode, set COMMONONN to 1’b1.
+ * 4. Place the USB PHY in Suspend mode by setting SUSPENDM0 to 1’b0.
+ * 5. Set BYPASSSEL0 to 1’b1.
+ * 6. To transmit data, controls BYPASSDMEN0, and BYPASSDMDATA0.
+ * To receive data, monitor FSVPLUS0.
+ *
+ * The actual code in the vendor kernel does some things differently.
+ */
+static int __init rk3288_init_usb_uart(struct regmap *grf)
+{
+ u32 val;
+ int ret;
+
+ /*
+ * COMMON_ON and DISABLE settings are described in the TRM,
+ * but were not present in the original code.
+ * Also disable the analog phy components to save power.
+ */
+ val = HIWORD_UPDATE(RK3288_UOC0_CON0_COMMON_ON_N
+ | RK3288_UOC0_CON0_DISABLE
+ | UOC_CON0_SIDDQ,
+ RK3288_UOC0_CON0_COMMON_ON_N
+ | RK3288_UOC0_CON0_DISABLE
+ | UOC_CON0_SIDDQ);
+ ret = regmap_write(grf, RK3288_UOC0_CON0, val);
+ if (ret)
+ return ret;
+
+ val = HIWORD_UPDATE(RK3288_UOC0_CON2_SOFT_CON_SEL,
+ RK3288_UOC0_CON2_SOFT_CON_SEL);
+ ret = regmap_write(grf, RK3288_UOC0_CON2, val);
+ if (ret)
+ return ret;
+
+ val = HIWORD_UPDATE(RK3288_UOC0_CON3_UTMI_OPMODE_NODRIVING
+ | RK3288_UOC0_CON3_UTMI_XCVRSEELCT_FSTRANSC
+ | RK3288_UOC0_CON3_UTMI_TERMSEL_FULLSPEED,
+ RK3288_UOC0_CON3_UTMI_SUSPENDN
+ | RK3288_UOC0_CON3_UTMI_OPMODE_MASK
+ | RK3288_UOC0_CON3_UTMI_XCVRSEELCT_MASK
+ | RK3288_UOC0_CON3_UTMI_TERMSEL_FULLSPEED);
+ ret = regmap_write(grf, RK3288_UOC0_CON3, val);
+ if (ret)
+ return ret;
+
+ val = HIWORD_UPDATE(RK3288_UOC0_CON3_BYPASSSEL
+ | RK3288_UOC0_CON3_BYPASSDMEN,
+ RK3288_UOC0_CON3_BYPASSSEL
+ | RK3288_UOC0_CON3_BYPASSDMEN);
+ ret = regmap_write(grf, RK3288_UOC0_CON3, val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct rockchip_usb_phy_pdata rk3288_pdata = {
+ .phys = (struct rockchip_usb_phys[]){
+ { .reg = 0x320, .pll_name = "sclk_otgphy0_480m" },
+ { .reg = 0x334, .pll_name = "sclk_otgphy1_480m" },
+ { .reg = 0x348, .pll_name = "sclk_otgphy2_480m" },
+ { /* sentinel */ }
+ },
+ .init_usb_uart = rk3288_init_usb_uart,
+ .usb_uart_phy = 0,
+};
+
+static int rockchip_usb_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rockchip_usb_phy_base *phy_base;
+ struct phy_provider *phy_provider;
+ const struct of_device_id *match;
+ struct device_node *child;
+ int err;
+
+ phy_base = devm_kzalloc(dev, sizeof(*phy_base), GFP_KERNEL);
+ if (!phy_base)
+ return -ENOMEM;
+
+ match = of_match_device(dev->driver->of_match_table, dev);
+ if (!match || !match->data) {
+ dev_err(dev, "missing phy data\n");
+ return -EINVAL;
+ }
+
+ phy_base->pdata = match->data;
+
+ phy_base->dev = dev;
+ phy_base->reg_base = ERR_PTR(-ENODEV);
+ if (dev->parent && dev->parent->of_node)
+ phy_base->reg_base = syscon_node_to_regmap(
+ dev->parent->of_node);
+ if (IS_ERR(phy_base->reg_base))
+ phy_base->reg_base = syscon_regmap_lookup_by_phandle(
+ dev->of_node, "rockchip,grf");
+ if (IS_ERR(phy_base->reg_base)) {
+ dev_err(&pdev->dev, "Missing rockchip,grf property\n");
+ return PTR_ERR(phy_base->reg_base);
+ }
+
+ for_each_available_child_of_node(dev->of_node, child) {
+ err = rockchip_usb_phy_init(phy_base, child);
+ if (err) {
+ of_node_put(child);
+ return err;
+ }
+ }
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id rockchip_usb_phy_dt_ids[] = {
+ { .compatible = "rockchip,rk3066a-usb-phy", .data = &rk3066a_pdata },
+ { .compatible = "rockchip,rk3188-usb-phy", .data = &rk3188_pdata },
+ { .compatible = "rockchip,rk3288-usb-phy", .data = &rk3288_pdata },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, rockchip_usb_phy_dt_ids);
+
+static struct platform_driver rockchip_usb_driver = {
+ .probe = rockchip_usb_phy_probe,
+ .driver = {
+ .name = "rockchip-usb-phy",
+ .of_match_table = rockchip_usb_phy_dt_ids,
+ },
+};
+
+module_platform_driver(rockchip_usb_driver);
+
+#ifndef MODULE
+static int __init rockchip_init_usb_uart(void)
+{
+ const struct of_device_id *match;
+ const struct rockchip_usb_phy_pdata *data;
+ struct device_node *np;
+ struct regmap *grf;
+ int ret;
+
+ if (!enable_usb_uart)
+ return 0;
+
+ np = of_find_matching_node_and_match(NULL, rockchip_usb_phy_dt_ids,
+ &match);
+ if (!np) {
+ pr_err("%s: failed to find usbphy node\n", __func__);
+ return -ENOTSUPP;
+ }
+
+ pr_debug("%s: using settings for %s\n", __func__, match->compatible);
+ data = match->data;
+
+ if (!data->init_usb_uart) {
+ pr_err("%s: usb-uart not available on %s\n",
+ __func__, match->compatible);
+ return -ENOTSUPP;
+ }
+
+ grf = ERR_PTR(-ENODEV);
+ if (np->parent)
+ grf = syscon_node_to_regmap(np->parent);
+ if (IS_ERR(grf))
+ grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
+ if (IS_ERR(grf)) {
+ pr_err("%s: Missing rockchip,grf property, %lu\n",
+ __func__, PTR_ERR(grf));
+ return PTR_ERR(grf);
+ }
+
+ ret = data->init_usb_uart(grf);
+ if (ret) {
+ pr_err("%s: could not init usb_uart, %d\n", __func__, ret);
+ enable_usb_uart = 0;
+ return ret;
+ }
+
+ return 0;
+}
+early_initcall(rockchip_init_usb_uart);
+
+static int __init rockchip_usb_uart(char *buf)
+{
+ enable_usb_uart = true;
+ return 0;
+}
+early_param("rockchip.usb_uart", rockchip_usb_uart);
+#endif
+
+MODULE_AUTHOR("Yunzhi Li <lyz@rock-chips.com>");
+MODULE_DESCRIPTION("Rockchip USB 2.0 PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/samsung/Kconfig b/drivers/phy/samsung/Kconfig
new file mode 100644
index 000000000..2a5d33cb0
--- /dev/null
+++ b/drivers/phy/samsung/Kconfig
@@ -0,0 +1,95 @@
+#
+# Phy drivers for Samsung platforms
+#
+config PHY_EXYNOS_DP_VIDEO
+ tristate "EXYNOS SoC series Display Port PHY driver"
+ depends on OF
+ depends on ARCH_EXYNOS || COMPILE_TEST
+ default ARCH_EXYNOS
+ select GENERIC_PHY
+ help
+ Support for Display Port PHY found on Samsung EXYNOS SoCs.
+
+config PHY_EXYNOS_MIPI_VIDEO
+ tristate "S5P/EXYNOS SoC series MIPI CSI-2/DSI PHY driver"
+ depends on HAS_IOMEM
+ depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST
+ select GENERIC_PHY
+ default y if ARCH_S5PV210 || ARCH_EXYNOS
+ help
+ Support for MIPI CSI-2 and MIPI DSI DPHY found on Samsung S5P
+ and EXYNOS SoCs.
+
+config PHY_EXYNOS_PCIE
+ bool "Exynos PCIe PHY driver"
+ depends on OF && (ARCH_EXYNOS || COMPILE_TEST)
+ select GENERIC_PHY
+ help
+ Enable PCIe PHY support for Exynos SoC series.
+ This driver provides PHY interface for Exynos PCIe controller.
+
+config PHY_SAMSUNG_USB2
+ tristate "Samsung USB 2.0 PHY driver"
+ depends on HAS_IOMEM
+ depends on USB_EHCI_EXYNOS || USB_OHCI_EXYNOS || USB_DWC2
+ select GENERIC_PHY
+ select MFD_SYSCON
+ default ARCH_EXYNOS
+ help
+ Enable this to support the Samsung USB 2.0 PHY driver for Samsung
+ SoCs. This driver provides the interface for USB 2.0 PHY. Support
+ for particular PHYs will be enabled based on the SoC type in addition
+ to this driver.
+
+config PHY_EXYNOS4210_USB2
+ bool
+ depends on PHY_SAMSUNG_USB2
+ default CPU_EXYNOS4210
+
+config PHY_EXYNOS4X12_USB2
+ bool
+ depends on PHY_SAMSUNG_USB2
+ default SOC_EXYNOS3250 || SOC_EXYNOS4412
+
+config PHY_EXYNOS5250_USB2
+ bool
+ depends on PHY_SAMSUNG_USB2
+ default SOC_EXYNOS5250 || SOC_EXYNOS5420
+
+config PHY_S5PV210_USB2
+ bool "Support for S5PV210"
+ depends on PHY_SAMSUNG_USB2
+ depends on ARCH_S5PV210
+ help
+ Enable USB PHY support for S5PV210. This option requires that Samsung
+ USB 2.0 PHY driver is enabled and means that support for this
+ particular SoC is compiled in the driver. In case of S5PV210 two phys
+ are available - device and host.
+
+config PHY_EXYNOS5_USBDRD
+ tristate "Exynos5 SoC series USB DRD PHY driver"
+ depends on ARCH_EXYNOS && OF
+ depends on HAS_IOMEM
+ depends on USB_DWC3_EXYNOS
+ select GENERIC_PHY
+ select MFD_SYSCON
+ default y
+ help
+ Enable USB DRD PHY support for Exynos 5 SoC series.
+ This driver provides PHY interface for USB 3.0 DRD controller
+ present on Exynos5 SoC series.
+
+config PHY_EXYNOS5250_SATA
+ tristate "Exynos5250 Sata SerDes/PHY driver"
+ depends on SOC_EXYNOS5250
+ depends on HAS_IOMEM
+ depends on OF
+ select GENERIC_PHY
+ select I2C
+ select I2C_S3C2410
+ select MFD_SYSCON
+ help
+ Enable this to support SATA SerDes/Phy found on Samsung's
+ Exynos5250 based SoCs.This SerDes/Phy supports SATA 1.5 Gb/s,
+ SATA 3.0 Gb/s, SATA 6.0 Gb/s speeds. It supports one SATA host
+ port to accept one SATA device.
diff --git a/drivers/phy/samsung/Makefile b/drivers/phy/samsung/Makefile
new file mode 100644
index 000000000..db9b1aa0d
--- /dev/null
+++ b/drivers/phy/samsung/Makefile
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_PHY_EXYNOS_DP_VIDEO) += phy-exynos-dp-video.o
+obj-$(CONFIG_PHY_EXYNOS_MIPI_VIDEO) += phy-exynos-mipi-video.o
+obj-$(CONFIG_PHY_EXYNOS_PCIE) += phy-exynos-pcie.o
+obj-$(CONFIG_PHY_SAMSUNG_USB2) += phy-exynos-usb2.o
+phy-exynos-usb2-y += phy-samsung-usb2.o
+phy-exynos-usb2-$(CONFIG_PHY_EXYNOS4210_USB2) += phy-exynos4210-usb2.o
+phy-exynos-usb2-$(CONFIG_PHY_EXYNOS4X12_USB2) += phy-exynos4x12-usb2.o
+phy-exynos-usb2-$(CONFIG_PHY_EXYNOS5250_USB2) += phy-exynos5250-usb2.o
+phy-exynos-usb2-$(CONFIG_PHY_S5PV210_USB2) += phy-s5pv210-usb2.o
+obj-$(CONFIG_PHY_EXYNOS5_USBDRD) += phy-exynos5-usbdrd.o
+obj-$(CONFIG_PHY_EXYNOS5250_SATA) += phy-exynos5250-sata.o
diff --git a/drivers/phy/samsung/phy-exynos-dp-video.c b/drivers/phy/samsung/phy-exynos-dp-video.c
new file mode 100644
index 000000000..2dd6dd1f3
--- /dev/null
+++ b/drivers/phy/samsung/phy-exynos-dp-video.c
@@ -0,0 +1,121 @@
+/*
+ * Samsung EXYNOS SoC series Display Port PHY driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ * Author: Jingoo Han <jg1.han@samsung.com>
+ *
+ * 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/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/soc/samsung/exynos-regs-pmu.h>
+
+struct exynos_dp_video_phy_drvdata {
+ u32 phy_ctrl_offset;
+};
+
+struct exynos_dp_video_phy {
+ struct regmap *regs;
+ const struct exynos_dp_video_phy_drvdata *drvdata;
+};
+
+static int exynos_dp_video_phy_power_on(struct phy *phy)
+{
+ struct exynos_dp_video_phy *state = phy_get_drvdata(phy);
+
+ /* Disable power isolation on DP-PHY */
+ return regmap_update_bits(state->regs, state->drvdata->phy_ctrl_offset,
+ EXYNOS4_PHY_ENABLE, EXYNOS4_PHY_ENABLE);
+}
+
+static int exynos_dp_video_phy_power_off(struct phy *phy)
+{
+ struct exynos_dp_video_phy *state = phy_get_drvdata(phy);
+
+ /* Enable power isolation on DP-PHY */
+ return regmap_update_bits(state->regs, state->drvdata->phy_ctrl_offset,
+ EXYNOS4_PHY_ENABLE, 0);
+}
+
+static const struct phy_ops exynos_dp_video_phy_ops = {
+ .power_on = exynos_dp_video_phy_power_on,
+ .power_off = exynos_dp_video_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static const struct exynos_dp_video_phy_drvdata exynos5250_dp_video_phy = {
+ .phy_ctrl_offset = EXYNOS5_DPTX_PHY_CONTROL,
+};
+
+static const struct exynos_dp_video_phy_drvdata exynos5420_dp_video_phy = {
+ .phy_ctrl_offset = EXYNOS5420_DPTX_PHY_CONTROL,
+};
+
+static const struct of_device_id exynos_dp_video_phy_of_match[] = {
+ {
+ .compatible = "samsung,exynos5250-dp-video-phy",
+ .data = &exynos5250_dp_video_phy,
+ }, {
+ .compatible = "samsung,exynos5420-dp-video-phy",
+ .data = &exynos5420_dp_video_phy,
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, exynos_dp_video_phy_of_match);
+
+static int exynos_dp_video_phy_probe(struct platform_device *pdev)
+{
+ struct exynos_dp_video_phy *state;
+ struct device *dev = &pdev->dev;
+ struct phy_provider *phy_provider;
+ struct phy *phy;
+
+ state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return -ENOMEM;
+
+ state->regs = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "samsung,pmu-syscon");
+ if (IS_ERR(state->regs)) {
+ dev_err(dev, "Failed to lookup PMU regmap\n");
+ return PTR_ERR(state->regs);
+ }
+
+ state->drvdata = of_device_get_match_data(dev);
+
+ phy = devm_phy_create(dev, NULL, &exynos_dp_video_phy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create Display Port PHY\n");
+ return PTR_ERR(phy);
+ }
+ phy_set_drvdata(phy, state);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static struct platform_driver exynos_dp_video_phy_driver = {
+ .probe = exynos_dp_video_phy_probe,
+ .driver = {
+ .name = "exynos-dp-video-phy",
+ .of_match_table = exynos_dp_video_phy_of_match,
+ }
+};
+module_platform_driver(exynos_dp_video_phy_driver);
+
+MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
+MODULE_DESCRIPTION("Samsung EXYNOS SoC DP PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/samsung/phy-exynos-mipi-video.c b/drivers/phy/samsung/phy-exynos-mipi-video.c
new file mode 100644
index 000000000..00d89599c
--- /dev/null
+++ b/drivers/phy/samsung/phy-exynos-mipi-video.c
@@ -0,0 +1,371 @@
+/*
+ * Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY driver
+ *
+ * Copyright (C) 2013,2016 Samsung Electronics Co., Ltd.
+ * Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
+ *
+ * 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/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+#include <linux/spinlock.h>
+#include <linux/soc/samsung/exynos-regs-pmu.h>
+#include <linux/mfd/syscon.h>
+
+enum exynos_mipi_phy_id {
+ EXYNOS_MIPI_PHY_ID_NONE = -1,
+ EXYNOS_MIPI_PHY_ID_CSIS0,
+ EXYNOS_MIPI_PHY_ID_DSIM0,
+ EXYNOS_MIPI_PHY_ID_CSIS1,
+ EXYNOS_MIPI_PHY_ID_DSIM1,
+ EXYNOS_MIPI_PHY_ID_CSIS2,
+ EXYNOS_MIPI_PHYS_NUM
+};
+
+enum exynos_mipi_phy_regmap_id {
+ EXYNOS_MIPI_REGMAP_PMU,
+ EXYNOS_MIPI_REGMAP_DISP,
+ EXYNOS_MIPI_REGMAP_CAM0,
+ EXYNOS_MIPI_REGMAP_CAM1,
+ EXYNOS_MIPI_REGMAPS_NUM
+};
+
+struct mipi_phy_device_desc {
+ int num_phys;
+ int num_regmaps;
+ const char *regmap_names[EXYNOS_MIPI_REGMAPS_NUM];
+ struct exynos_mipi_phy_desc {
+ enum exynos_mipi_phy_id coupled_phy_id;
+ u32 enable_val;
+ unsigned int enable_reg;
+ enum exynos_mipi_phy_regmap_id enable_map;
+ u32 resetn_val;
+ unsigned int resetn_reg;
+ enum exynos_mipi_phy_regmap_id resetn_map;
+ } phys[EXYNOS_MIPI_PHYS_NUM];
+};
+
+static const struct mipi_phy_device_desc s5pv210_mipi_phy = {
+ .num_regmaps = 1,
+ .regmap_names = {"syscon"},
+ .num_phys = 4,
+ .phys = {
+ {
+ /* EXYNOS_MIPI_PHY_ID_CSIS0 */
+ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0,
+ .enable_val = EXYNOS4_PHY_ENABLE,
+ .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0),
+ .enable_map = EXYNOS_MIPI_REGMAP_PMU,
+ .resetn_val = EXYNOS4_MIPI_PHY_SRESETN,
+ .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(0),
+ .resetn_map = EXYNOS_MIPI_REGMAP_PMU,
+ }, {
+ /* EXYNOS_MIPI_PHY_ID_DSIM0 */
+ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0,
+ .enable_val = EXYNOS4_PHY_ENABLE,
+ .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0),
+ .enable_map = EXYNOS_MIPI_REGMAP_PMU,
+ .resetn_val = EXYNOS4_MIPI_PHY_MRESETN,
+ .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(0),
+ .resetn_map = EXYNOS_MIPI_REGMAP_PMU,
+ }, {
+ /* EXYNOS_MIPI_PHY_ID_CSIS1 */
+ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM1,
+ .enable_val = EXYNOS4_PHY_ENABLE,
+ .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1),
+ .enable_map = EXYNOS_MIPI_REGMAP_PMU,
+ .resetn_val = EXYNOS4_MIPI_PHY_SRESETN,
+ .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(1),
+ .resetn_map = EXYNOS_MIPI_REGMAP_PMU,
+ }, {
+ /* EXYNOS_MIPI_PHY_ID_DSIM1 */
+ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS1,
+ .enable_val = EXYNOS4_PHY_ENABLE,
+ .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1),
+ .enable_map = EXYNOS_MIPI_REGMAP_PMU,
+ .resetn_val = EXYNOS4_MIPI_PHY_MRESETN,
+ .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(1),
+ .resetn_map = EXYNOS_MIPI_REGMAP_PMU,
+ },
+ },
+};
+
+static const struct mipi_phy_device_desc exynos5420_mipi_phy = {
+ .num_regmaps = 1,
+ .regmap_names = {"syscon"},
+ .num_phys = 5,
+ .phys = {
+ {
+ /* EXYNOS_MIPI_PHY_ID_CSIS0 */
+ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0,
+ .enable_val = EXYNOS4_PHY_ENABLE,
+ .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(0),
+ .enable_map = EXYNOS_MIPI_REGMAP_PMU,
+ .resetn_val = EXYNOS4_MIPI_PHY_SRESETN,
+ .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(0),
+ .resetn_map = EXYNOS_MIPI_REGMAP_PMU,
+ }, {
+ /* EXYNOS_MIPI_PHY_ID_DSIM0 */
+ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0,
+ .enable_val = EXYNOS4_PHY_ENABLE,
+ .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(0),
+ .enable_map = EXYNOS_MIPI_REGMAP_PMU,
+ .resetn_val = EXYNOS4_MIPI_PHY_MRESETN,
+ .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(0),
+ .resetn_map = EXYNOS_MIPI_REGMAP_PMU,
+ }, {
+ /* EXYNOS_MIPI_PHY_ID_CSIS1 */
+ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM1,
+ .enable_val = EXYNOS4_PHY_ENABLE,
+ .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(1),
+ .enable_map = EXYNOS_MIPI_REGMAP_PMU,
+ .resetn_val = EXYNOS4_MIPI_PHY_SRESETN,
+ .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(1),
+ .resetn_map = EXYNOS_MIPI_REGMAP_PMU,
+ }, {
+ /* EXYNOS_MIPI_PHY_ID_DSIM1 */
+ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS1,
+ .enable_val = EXYNOS4_PHY_ENABLE,
+ .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(1),
+ .enable_map = EXYNOS_MIPI_REGMAP_PMU,
+ .resetn_val = EXYNOS4_MIPI_PHY_MRESETN,
+ .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(1),
+ .resetn_map = EXYNOS_MIPI_REGMAP_PMU,
+ }, {
+ /* EXYNOS_MIPI_PHY_ID_CSIS2 */
+ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE,
+ .enable_val = EXYNOS4_PHY_ENABLE,
+ .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(2),
+ .enable_map = EXYNOS_MIPI_REGMAP_PMU,
+ .resetn_val = EXYNOS4_MIPI_PHY_SRESETN,
+ .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(2),
+ .resetn_map = EXYNOS_MIPI_REGMAP_PMU,
+ },
+ },
+};
+
+#define EXYNOS5433_SYSREG_DISP_MIPI_PHY 0x100C
+#define EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON 0x1014
+#define EXYNOS5433_SYSREG_CAM1_MIPI_DPHY_CON 0x1020
+
+static const struct mipi_phy_device_desc exynos5433_mipi_phy = {
+ .num_regmaps = 4,
+ .regmap_names = {
+ "samsung,pmu-syscon",
+ "samsung,disp-sysreg",
+ "samsung,cam0-sysreg",
+ "samsung,cam1-sysreg"
+ },
+ .num_phys = 5,
+ .phys = {
+ {
+ /* EXYNOS_MIPI_PHY_ID_CSIS0 */
+ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0,
+ .enable_val = EXYNOS4_PHY_ENABLE,
+ .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0),
+ .enable_map = EXYNOS_MIPI_REGMAP_PMU,
+ .resetn_val = BIT(0),
+ .resetn_reg = EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON,
+ .resetn_map = EXYNOS_MIPI_REGMAP_CAM0,
+ }, {
+ /* EXYNOS_MIPI_PHY_ID_DSIM0 */
+ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0,
+ .enable_val = EXYNOS4_PHY_ENABLE,
+ .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0),
+ .enable_map = EXYNOS_MIPI_REGMAP_PMU,
+ .resetn_val = BIT(0),
+ .resetn_reg = EXYNOS5433_SYSREG_DISP_MIPI_PHY,
+ .resetn_map = EXYNOS_MIPI_REGMAP_DISP,
+ }, {
+ /* EXYNOS_MIPI_PHY_ID_CSIS1 */
+ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE,
+ .enable_val = EXYNOS4_PHY_ENABLE,
+ .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1),
+ .enable_map = EXYNOS_MIPI_REGMAP_PMU,
+ .resetn_val = BIT(1),
+ .resetn_reg = EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON,
+ .resetn_map = EXYNOS_MIPI_REGMAP_CAM0,
+ }, {
+ /* EXYNOS_MIPI_PHY_ID_DSIM1 */
+ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE,
+ .enable_val = EXYNOS4_PHY_ENABLE,
+ .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1),
+ .enable_map = EXYNOS_MIPI_REGMAP_PMU,
+ .resetn_val = BIT(1),
+ .resetn_reg = EXYNOS5433_SYSREG_DISP_MIPI_PHY,
+ .resetn_map = EXYNOS_MIPI_REGMAP_DISP,
+ }, {
+ /* EXYNOS_MIPI_PHY_ID_CSIS2 */
+ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE,
+ .enable_val = EXYNOS4_PHY_ENABLE,
+ .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(2),
+ .enable_map = EXYNOS_MIPI_REGMAP_PMU,
+ .resetn_val = BIT(0),
+ .resetn_reg = EXYNOS5433_SYSREG_CAM1_MIPI_DPHY_CON,
+ .resetn_map = EXYNOS_MIPI_REGMAP_CAM1,
+ },
+ },
+};
+
+struct exynos_mipi_video_phy {
+ struct regmap *regmaps[EXYNOS_MIPI_REGMAPS_NUM];
+ int num_phys;
+ struct video_phy_desc {
+ struct phy *phy;
+ unsigned int index;
+ const struct exynos_mipi_phy_desc *data;
+ } phys[EXYNOS_MIPI_PHYS_NUM];
+ spinlock_t slock;
+};
+
+static int __set_phy_state(const struct exynos_mipi_phy_desc *data,
+ struct exynos_mipi_video_phy *state, unsigned int on)
+{
+ struct regmap *enable_map = state->regmaps[data->enable_map];
+ struct regmap *resetn_map = state->regmaps[data->resetn_map];
+
+ spin_lock(&state->slock);
+
+ /* disable in PMU sysreg */
+ if (!on && data->coupled_phy_id >= 0 &&
+ state->phys[data->coupled_phy_id].phy->power_count == 0)
+ regmap_update_bits(enable_map, data->enable_reg,
+ data->enable_val, 0);
+ /* PHY reset */
+ if (on)
+ regmap_update_bits(resetn_map, data->resetn_reg,
+ data->resetn_val, data->resetn_val);
+ else
+ regmap_update_bits(resetn_map, data->resetn_reg,
+ data->resetn_val, 0);
+ /* enable in PMU sysreg */
+ if (on)
+ regmap_update_bits(enable_map, data->enable_reg,
+ data->enable_val, data->enable_val);
+
+ spin_unlock(&state->slock);
+
+ return 0;
+}
+
+#define to_mipi_video_phy(desc) \
+ container_of((desc), struct exynos_mipi_video_phy, phys[(desc)->index])
+
+static int exynos_mipi_video_phy_power_on(struct phy *phy)
+{
+ struct video_phy_desc *phy_desc = phy_get_drvdata(phy);
+ struct exynos_mipi_video_phy *state = to_mipi_video_phy(phy_desc);
+
+ return __set_phy_state(phy_desc->data, state, 1);
+}
+
+static int exynos_mipi_video_phy_power_off(struct phy *phy)
+{
+ struct video_phy_desc *phy_desc = phy_get_drvdata(phy);
+ struct exynos_mipi_video_phy *state = to_mipi_video_phy(phy_desc);
+
+ return __set_phy_state(phy_desc->data, state, 0);
+}
+
+static struct phy *exynos_mipi_video_phy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct exynos_mipi_video_phy *state = dev_get_drvdata(dev);
+
+ if (WARN_ON(args->args[0] >= state->num_phys))
+ return ERR_PTR(-ENODEV);
+
+ return state->phys[args->args[0]].phy;
+}
+
+static const struct phy_ops exynos_mipi_video_phy_ops = {
+ .power_on = exynos_mipi_video_phy_power_on,
+ .power_off = exynos_mipi_video_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int exynos_mipi_video_phy_probe(struct platform_device *pdev)
+{
+ const struct mipi_phy_device_desc *phy_dev;
+ struct exynos_mipi_video_phy *state;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct phy_provider *phy_provider;
+ unsigned int i;
+
+ phy_dev = of_device_get_match_data(dev);
+ if (!phy_dev)
+ return -ENODEV;
+
+ state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return -ENOMEM;
+
+ for (i = 0; i < phy_dev->num_regmaps; i++) {
+ state->regmaps[i] = syscon_regmap_lookup_by_phandle(np,
+ phy_dev->regmap_names[i]);
+ if (IS_ERR(state->regmaps[i]))
+ return PTR_ERR(state->regmaps[i]);
+ }
+ state->num_phys = phy_dev->num_phys;
+ spin_lock_init(&state->slock);
+
+ dev_set_drvdata(dev, state);
+
+ for (i = 0; i < state->num_phys; i++) {
+ struct phy *phy = devm_phy_create(dev, NULL,
+ &exynos_mipi_video_phy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create PHY %d\n", i);
+ return PTR_ERR(phy);
+ }
+
+ state->phys[i].phy = phy;
+ state->phys[i].index = i;
+ state->phys[i].data = &phy_dev->phys[i];
+ phy_set_drvdata(phy, &state->phys[i]);
+ }
+
+ phy_provider = devm_of_phy_provider_register(dev,
+ exynos_mipi_video_phy_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id exynos_mipi_video_phy_of_match[] = {
+ {
+ .compatible = "samsung,s5pv210-mipi-video-phy",
+ .data = &s5pv210_mipi_phy,
+ }, {
+ .compatible = "samsung,exynos5420-mipi-video-phy",
+ .data = &exynos5420_mipi_phy,
+ }, {
+ .compatible = "samsung,exynos5433-mipi-video-phy",
+ .data = &exynos5433_mipi_phy,
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, exynos_mipi_video_phy_of_match);
+
+static struct platform_driver exynos_mipi_video_phy_driver = {
+ .probe = exynos_mipi_video_phy_probe,
+ .driver = {
+ .of_match_table = exynos_mipi_video_phy_of_match,
+ .name = "exynos-mipi-video-phy",
+ }
+};
+module_platform_driver(exynos_mipi_video_phy_driver);
+
+MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC MIPI CSI-2/DSI PHY driver");
+MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/samsung/phy-exynos-pcie.c b/drivers/phy/samsung/phy-exynos-pcie.c
new file mode 100644
index 000000000..a89c12faf
--- /dev/null
+++ b/drivers/phy/samsung/phy-exynos-pcie.c
@@ -0,0 +1,281 @@
+/*
+ * Samsung EXYNOS SoC series PCIe PHY driver
+ *
+ * Phy provider for PCIe controller on Exynos SoC series
+ *
+ * Copyright (C) 2017 Samsung Electronics Co., Ltd.
+ * Jaehoon Chung <jh80.chung@samsung.com>
+ *
+ * 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/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/init.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+
+/* PCIe Purple registers */
+#define PCIE_PHY_GLOBAL_RESET 0x000
+#define PCIE_PHY_COMMON_RESET 0x004
+#define PCIE_PHY_CMN_REG 0x008
+#define PCIE_PHY_MAC_RESET 0x00c
+#define PCIE_PHY_PLL_LOCKED 0x010
+#define PCIE_PHY_TRSVREG_RESET 0x020
+#define PCIE_PHY_TRSV_RESET 0x024
+
+/* PCIe PHY registers */
+#define PCIE_PHY_IMPEDANCE 0x004
+#define PCIE_PHY_PLL_DIV_0 0x008
+#define PCIE_PHY_PLL_BIAS 0x00c
+#define PCIE_PHY_DCC_FEEDBACK 0x014
+#define PCIE_PHY_PLL_DIV_1 0x05c
+#define PCIE_PHY_COMMON_POWER 0x064
+#define PCIE_PHY_COMMON_PD_CMN BIT(3)
+#define PCIE_PHY_TRSV0_EMP_LVL 0x084
+#define PCIE_PHY_TRSV0_DRV_LVL 0x088
+#define PCIE_PHY_TRSV0_RXCDR 0x0ac
+#define PCIE_PHY_TRSV0_POWER 0x0c4
+#define PCIE_PHY_TRSV0_PD_TSV BIT(7)
+#define PCIE_PHY_TRSV0_LVCC 0x0dc
+#define PCIE_PHY_TRSV1_EMP_LVL 0x144
+#define PCIE_PHY_TRSV1_RXCDR 0x16c
+#define PCIE_PHY_TRSV1_POWER 0x184
+#define PCIE_PHY_TRSV1_PD_TSV BIT(7)
+#define PCIE_PHY_TRSV1_LVCC 0x19c
+#define PCIE_PHY_TRSV2_EMP_LVL 0x204
+#define PCIE_PHY_TRSV2_RXCDR 0x22c
+#define PCIE_PHY_TRSV2_POWER 0x244
+#define PCIE_PHY_TRSV2_PD_TSV BIT(7)
+#define PCIE_PHY_TRSV2_LVCC 0x25c
+#define PCIE_PHY_TRSV3_EMP_LVL 0x2c4
+#define PCIE_PHY_TRSV3_RXCDR 0x2ec
+#define PCIE_PHY_TRSV3_POWER 0x304
+#define PCIE_PHY_TRSV3_PD_TSV BIT(7)
+#define PCIE_PHY_TRSV3_LVCC 0x31c
+
+struct exynos_pcie_phy_data {
+ const struct phy_ops *ops;
+};
+
+/* For Exynos pcie phy */
+struct exynos_pcie_phy {
+ const struct exynos_pcie_phy_data *drv_data;
+ void __iomem *phy_base;
+ void __iomem *blk_base; /* For exynos5440 */
+};
+
+static void exynos_pcie_phy_writel(void __iomem *base, u32 val, u32 offset)
+{
+ writel(val, base + offset);
+}
+
+static u32 exynos_pcie_phy_readl(void __iomem *base, u32 offset)
+{
+ return readl(base + offset);
+}
+
+/* For Exynos5440 specific functions */
+static int exynos5440_pcie_phy_init(struct phy *phy)
+{
+ struct exynos_pcie_phy *ep = phy_get_drvdata(phy);
+
+ /* DCC feedback control off */
+ exynos_pcie_phy_writel(ep->phy_base, 0x29, PCIE_PHY_DCC_FEEDBACK);
+
+ /* set TX/RX impedance */
+ exynos_pcie_phy_writel(ep->phy_base, 0xd5, PCIE_PHY_IMPEDANCE);
+
+ /* set 50Mhz PHY clock */
+ exynos_pcie_phy_writel(ep->phy_base, 0x14, PCIE_PHY_PLL_DIV_0);
+ exynos_pcie_phy_writel(ep->phy_base, 0x12, PCIE_PHY_PLL_DIV_1);
+
+ /* set TX Differential output for lane 0 */
+ exynos_pcie_phy_writel(ep->phy_base, 0x7f, PCIE_PHY_TRSV0_DRV_LVL);
+
+ /* set TX Pre-emphasis Level Control for lane 0 to minimum */
+ exynos_pcie_phy_writel(ep->phy_base, 0x0, PCIE_PHY_TRSV0_EMP_LVL);
+
+ /* set RX clock and data recovery bandwidth */
+ exynos_pcie_phy_writel(ep->phy_base, 0xe7, PCIE_PHY_PLL_BIAS);
+ exynos_pcie_phy_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV0_RXCDR);
+ exynos_pcie_phy_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV1_RXCDR);
+ exynos_pcie_phy_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV2_RXCDR);
+ exynos_pcie_phy_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV3_RXCDR);
+
+ /* change TX Pre-emphasis Level Control for lanes */
+ exynos_pcie_phy_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV0_EMP_LVL);
+ exynos_pcie_phy_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV1_EMP_LVL);
+ exynos_pcie_phy_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV2_EMP_LVL);
+ exynos_pcie_phy_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV3_EMP_LVL);
+
+ /* set LVCC */
+ exynos_pcie_phy_writel(ep->phy_base, 0x20, PCIE_PHY_TRSV0_LVCC);
+ exynos_pcie_phy_writel(ep->phy_base, 0xa0, PCIE_PHY_TRSV1_LVCC);
+ exynos_pcie_phy_writel(ep->phy_base, 0xa0, PCIE_PHY_TRSV2_LVCC);
+ exynos_pcie_phy_writel(ep->phy_base, 0xa0, PCIE_PHY_TRSV3_LVCC);
+
+ /* pulse for common reset */
+ exynos_pcie_phy_writel(ep->blk_base, 1, PCIE_PHY_COMMON_RESET);
+ udelay(500);
+ exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_COMMON_RESET);
+
+ return 0;
+}
+
+static int exynos5440_pcie_phy_power_on(struct phy *phy)
+{
+ struct exynos_pcie_phy *ep = phy_get_drvdata(phy);
+ u32 val;
+
+ exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_COMMON_RESET);
+ exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_CMN_REG);
+ exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_TRSVREG_RESET);
+ exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_TRSV_RESET);
+
+ val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_COMMON_POWER);
+ val &= ~PCIE_PHY_COMMON_PD_CMN;
+ exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_COMMON_POWER);
+
+ val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV0_POWER);
+ val &= ~PCIE_PHY_TRSV0_PD_TSV;
+ exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV0_POWER);
+
+ val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV1_POWER);
+ val &= ~PCIE_PHY_TRSV1_PD_TSV;
+ exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV1_POWER);
+
+ val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV2_POWER);
+ val &= ~PCIE_PHY_TRSV2_PD_TSV;
+ exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV2_POWER);
+
+ val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV3_POWER);
+ val &= ~PCIE_PHY_TRSV3_PD_TSV;
+ exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV3_POWER);
+
+ return 0;
+}
+
+static int exynos5440_pcie_phy_power_off(struct phy *phy)
+{
+ struct exynos_pcie_phy *ep = phy_get_drvdata(phy);
+ u32 val;
+
+ if (readl_poll_timeout(ep->phy_base + PCIE_PHY_PLL_LOCKED, val,
+ (val != 0), 1, 500)) {
+ dev_err(&phy->dev, "PLL Locked: 0x%x\n", val);
+ return -ETIMEDOUT;
+ }
+
+ val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_COMMON_POWER);
+ val |= PCIE_PHY_COMMON_PD_CMN;
+ exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_COMMON_POWER);
+
+ val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV0_POWER);
+ val |= PCIE_PHY_TRSV0_PD_TSV;
+ exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV0_POWER);
+
+ val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV1_POWER);
+ val |= PCIE_PHY_TRSV1_PD_TSV;
+ exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV1_POWER);
+
+ val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV2_POWER);
+ val |= PCIE_PHY_TRSV2_PD_TSV;
+ exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV2_POWER);
+
+ val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV3_POWER);
+ val |= PCIE_PHY_TRSV3_PD_TSV;
+ exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV3_POWER);
+
+ return 0;
+}
+
+static int exynos5440_pcie_phy_reset(struct phy *phy)
+{
+ struct exynos_pcie_phy *ep = phy_get_drvdata(phy);
+
+ exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_MAC_RESET);
+ exynos_pcie_phy_writel(ep->blk_base, 1, PCIE_PHY_GLOBAL_RESET);
+ exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_GLOBAL_RESET);
+
+ return 0;
+}
+
+static const struct phy_ops exynos5440_phy_ops = {
+ .init = exynos5440_pcie_phy_init,
+ .power_on = exynos5440_pcie_phy_power_on,
+ .power_off = exynos5440_pcie_phy_power_off,
+ .reset = exynos5440_pcie_phy_reset,
+ .owner = THIS_MODULE,
+};
+
+static const struct exynos_pcie_phy_data exynos5440_pcie_phy_data = {
+ .ops = &exynos5440_phy_ops,
+};
+
+static const struct of_device_id exynos_pcie_phy_match[] = {
+ {
+ .compatible = "samsung,exynos5440-pcie-phy",
+ .data = &exynos5440_pcie_phy_data,
+ },
+ {},
+};
+
+static int exynos_pcie_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct exynos_pcie_phy *exynos_phy;
+ struct phy *generic_phy;
+ struct phy_provider *phy_provider;
+ struct resource *res;
+ const struct exynos_pcie_phy_data *drv_data;
+
+ drv_data = of_device_get_match_data(dev);
+ if (!drv_data)
+ return -ENODEV;
+
+ exynos_phy = devm_kzalloc(dev, sizeof(*exynos_phy), GFP_KERNEL);
+ if (!exynos_phy)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ exynos_phy->phy_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(exynos_phy->phy_base))
+ return PTR_ERR(exynos_phy->phy_base);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ exynos_phy->blk_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(exynos_phy->blk_base))
+ return PTR_ERR(exynos_phy->blk_base);
+
+ exynos_phy->drv_data = drv_data;
+
+ generic_phy = devm_phy_create(dev, dev->of_node, drv_data->ops);
+ if (IS_ERR(generic_phy)) {
+ dev_err(dev, "failed to create PHY\n");
+ return PTR_ERR(generic_phy);
+ }
+
+ phy_set_drvdata(generic_phy, exynos_phy);
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static struct platform_driver exynos_pcie_phy_driver = {
+ .probe = exynos_pcie_phy_probe,
+ .driver = {
+ .of_match_table = exynos_pcie_phy_match,
+ .name = "exynos_pcie_phy",
+ }
+};
+
+builtin_platform_driver(exynos_pcie_phy_driver);
diff --git a/drivers/phy/samsung/phy-exynos4210-usb2.c b/drivers/phy/samsung/phy-exynos4210-usb2.c
new file mode 100644
index 000000000..1f50e1004
--- /dev/null
+++ b/drivers/phy/samsung/phy-exynos4210-usb2.c
@@ -0,0 +1,260 @@
+/*
+ * Samsung SoC USB 1.1/2.0 PHY driver - Exynos 4210 support
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ * Author: Kamil Debski <k.debski@samsung.com>
+ *
+ * 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/delay.h>
+#include <linux/io.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+#include "phy-samsung-usb2.h"
+
+/* Exynos USB PHY registers */
+
+/* PHY power control */
+#define EXYNOS_4210_UPHYPWR 0x0
+
+#define EXYNOS_4210_UPHYPWR_PHY0_SUSPEND BIT(0)
+#define EXYNOS_4210_UPHYPWR_PHY0_PWR BIT(3)
+#define EXYNOS_4210_UPHYPWR_PHY0_OTG_PWR BIT(4)
+#define EXYNOS_4210_UPHYPWR_PHY0_SLEEP BIT(5)
+#define EXYNOS_4210_UPHYPWR_PHY0 ( \
+ EXYNOS_4210_UPHYPWR_PHY0_SUSPEND | \
+ EXYNOS_4210_UPHYPWR_PHY0_PWR | \
+ EXYNOS_4210_UPHYPWR_PHY0_OTG_PWR | \
+ EXYNOS_4210_UPHYPWR_PHY0_SLEEP)
+
+#define EXYNOS_4210_UPHYPWR_PHY1_SUSPEND BIT(6)
+#define EXYNOS_4210_UPHYPWR_PHY1_PWR BIT(7)
+#define EXYNOS_4210_UPHYPWR_PHY1_SLEEP BIT(8)
+#define EXYNOS_4210_UPHYPWR_PHY1 ( \
+ EXYNOS_4210_UPHYPWR_PHY1_SUSPEND | \
+ EXYNOS_4210_UPHYPWR_PHY1_PWR | \
+ EXYNOS_4210_UPHYPWR_PHY1_SLEEP)
+
+#define EXYNOS_4210_UPHYPWR_HSIC0_SUSPEND BIT(9)
+#define EXYNOS_4210_UPHYPWR_HSIC0_SLEEP BIT(10)
+#define EXYNOS_4210_UPHYPWR_HSIC0 ( \
+ EXYNOS_4210_UPHYPWR_HSIC0_SUSPEND | \
+ EXYNOS_4210_UPHYPWR_HSIC0_SLEEP)
+
+#define EXYNOS_4210_UPHYPWR_HSIC1_SUSPEND BIT(11)
+#define EXYNOS_4210_UPHYPWR_HSIC1_SLEEP BIT(12)
+#define EXYNOS_4210_UPHYPWR_HSIC1 ( \
+ EXYNOS_4210_UPHYPWR_HSIC1_SUSPEND | \
+ EXYNOS_4210_UPHYPWR_HSIC1_SLEEP)
+
+/* PHY clock control */
+#define EXYNOS_4210_UPHYCLK 0x4
+
+#define EXYNOS_4210_UPHYCLK_PHYFSEL_MASK (0x3 << 0)
+#define EXYNOS_4210_UPHYCLK_PHYFSEL_OFFSET 0
+#define EXYNOS_4210_UPHYCLK_PHYFSEL_48MHZ (0x0 << 0)
+#define EXYNOS_4210_UPHYCLK_PHYFSEL_24MHZ (0x3 << 0)
+#define EXYNOS_4210_UPHYCLK_PHYFSEL_12MHZ (0x2 << 0)
+
+#define EXYNOS_4210_UPHYCLK_PHY0_ID_PULLUP BIT(2)
+#define EXYNOS_4210_UPHYCLK_PHY0_COMMON_ON BIT(4)
+#define EXYNOS_4210_UPHYCLK_PHY1_COMMON_ON BIT(7)
+
+/* PHY reset control */
+#define EXYNOS_4210_UPHYRST 0x8
+
+#define EXYNOS_4210_URSTCON_PHY0 BIT(0)
+#define EXYNOS_4210_URSTCON_OTG_HLINK BIT(1)
+#define EXYNOS_4210_URSTCON_OTG_PHYLINK BIT(2)
+#define EXYNOS_4210_URSTCON_PHY1_ALL BIT(3)
+#define EXYNOS_4210_URSTCON_PHY1_P0 BIT(4)
+#define EXYNOS_4210_URSTCON_PHY1_P1P2 BIT(5)
+#define EXYNOS_4210_URSTCON_HOST_LINK_ALL BIT(6)
+#define EXYNOS_4210_URSTCON_HOST_LINK_P0 BIT(7)
+#define EXYNOS_4210_URSTCON_HOST_LINK_P1 BIT(8)
+#define EXYNOS_4210_URSTCON_HOST_LINK_P2 BIT(9)
+
+/* Isolation, configured in the power management unit */
+#define EXYNOS_4210_USB_ISOL_DEVICE_OFFSET 0x704
+#define EXYNOS_4210_USB_ISOL_DEVICE BIT(0)
+#define EXYNOS_4210_USB_ISOL_HOST_OFFSET 0x708
+#define EXYNOS_4210_USB_ISOL_HOST BIT(0)
+
+/* USBYPHY1 Floating prevention */
+#define EXYNOS_4210_UPHY1CON 0x34
+#define EXYNOS_4210_UPHY1CON_FLOAT_PREVENTION 0x1
+
+/* Mode switching SUB Device <-> Host */
+#define EXYNOS_4210_MODE_SWITCH_OFFSET 0x21c
+#define EXYNOS_4210_MODE_SWITCH_MASK 1
+#define EXYNOS_4210_MODE_SWITCH_DEVICE 0
+#define EXYNOS_4210_MODE_SWITCH_HOST 1
+
+enum exynos4210_phy_id {
+ EXYNOS4210_DEVICE,
+ EXYNOS4210_HOST,
+ EXYNOS4210_HSIC0,
+ EXYNOS4210_HSIC1,
+ EXYNOS4210_NUM_PHYS,
+};
+
+/*
+ * exynos4210_rate_to_clk() converts the supplied clock rate to the value that
+ * can be written to the phy register.
+ */
+static int exynos4210_rate_to_clk(unsigned long rate, u32 *reg)
+{
+ switch (rate) {
+ case 12 * MHZ:
+ *reg = EXYNOS_4210_UPHYCLK_PHYFSEL_12MHZ;
+ break;
+ case 24 * MHZ:
+ *reg = EXYNOS_4210_UPHYCLK_PHYFSEL_24MHZ;
+ break;
+ case 48 * MHZ:
+ *reg = EXYNOS_4210_UPHYCLK_PHYFSEL_48MHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void exynos4210_isol(struct samsung_usb2_phy_instance *inst, bool on)
+{
+ struct samsung_usb2_phy_driver *drv = inst->drv;
+ u32 offset;
+ u32 mask;
+
+ switch (inst->cfg->id) {
+ case EXYNOS4210_DEVICE:
+ offset = EXYNOS_4210_USB_ISOL_DEVICE_OFFSET;
+ mask = EXYNOS_4210_USB_ISOL_DEVICE;
+ break;
+ case EXYNOS4210_HOST:
+ offset = EXYNOS_4210_USB_ISOL_HOST_OFFSET;
+ mask = EXYNOS_4210_USB_ISOL_HOST;
+ break;
+ default:
+ return;
+ }
+
+ regmap_update_bits(drv->reg_pmu, offset, mask, on ? 0 : mask);
+}
+
+static void exynos4210_phy_pwr(struct samsung_usb2_phy_instance *inst, bool on)
+{
+ struct samsung_usb2_phy_driver *drv = inst->drv;
+ u32 rstbits = 0;
+ u32 phypwr = 0;
+ u32 rst;
+ u32 pwr;
+ u32 clk;
+
+ switch (inst->cfg->id) {
+ case EXYNOS4210_DEVICE:
+ phypwr = EXYNOS_4210_UPHYPWR_PHY0;
+ rstbits = EXYNOS_4210_URSTCON_PHY0;
+ break;
+ case EXYNOS4210_HOST:
+ phypwr = EXYNOS_4210_UPHYPWR_PHY1;
+ rstbits = EXYNOS_4210_URSTCON_PHY1_ALL |
+ EXYNOS_4210_URSTCON_PHY1_P0 |
+ EXYNOS_4210_URSTCON_PHY1_P1P2 |
+ EXYNOS_4210_URSTCON_HOST_LINK_ALL |
+ EXYNOS_4210_URSTCON_HOST_LINK_P0;
+ writel(on, drv->reg_phy + EXYNOS_4210_UPHY1CON);
+ break;
+ case EXYNOS4210_HSIC0:
+ phypwr = EXYNOS_4210_UPHYPWR_HSIC0;
+ rstbits = EXYNOS_4210_URSTCON_PHY1_P1P2 |
+ EXYNOS_4210_URSTCON_HOST_LINK_P1;
+ break;
+ case EXYNOS4210_HSIC1:
+ phypwr = EXYNOS_4210_UPHYPWR_HSIC1;
+ rstbits = EXYNOS_4210_URSTCON_PHY1_P1P2 |
+ EXYNOS_4210_URSTCON_HOST_LINK_P2;
+ break;
+ }
+
+ if (on) {
+ clk = readl(drv->reg_phy + EXYNOS_4210_UPHYCLK);
+ clk &= ~EXYNOS_4210_UPHYCLK_PHYFSEL_MASK;
+ clk |= drv->ref_reg_val << EXYNOS_4210_UPHYCLK_PHYFSEL_OFFSET;
+ writel(clk, drv->reg_phy + EXYNOS_4210_UPHYCLK);
+
+ pwr = readl(drv->reg_phy + EXYNOS_4210_UPHYPWR);
+ pwr &= ~phypwr;
+ writel(pwr, drv->reg_phy + EXYNOS_4210_UPHYPWR);
+
+ rst = readl(drv->reg_phy + EXYNOS_4210_UPHYRST);
+ rst |= rstbits;
+ writel(rst, drv->reg_phy + EXYNOS_4210_UPHYRST);
+ udelay(10);
+ rst &= ~rstbits;
+ writel(rst, drv->reg_phy + EXYNOS_4210_UPHYRST);
+ /* The following delay is necessary for the reset sequence to be
+ * completed */
+ udelay(80);
+ } else {
+ pwr = readl(drv->reg_phy + EXYNOS_4210_UPHYPWR);
+ pwr |= phypwr;
+ writel(pwr, drv->reg_phy + EXYNOS_4210_UPHYPWR);
+ }
+}
+
+static int exynos4210_power_on(struct samsung_usb2_phy_instance *inst)
+{
+ /* Order of initialisation is important - first power then isolation */
+ exynos4210_phy_pwr(inst, 1);
+ exynos4210_isol(inst, 0);
+
+ return 0;
+}
+
+static int exynos4210_power_off(struct samsung_usb2_phy_instance *inst)
+{
+ exynos4210_isol(inst, 1);
+ exynos4210_phy_pwr(inst, 0);
+
+ return 0;
+}
+
+
+static const struct samsung_usb2_common_phy exynos4210_phys[] = {
+ {
+ .label = "device",
+ .id = EXYNOS4210_DEVICE,
+ .power_on = exynos4210_power_on,
+ .power_off = exynos4210_power_off,
+ },
+ {
+ .label = "host",
+ .id = EXYNOS4210_HOST,
+ .power_on = exynos4210_power_on,
+ .power_off = exynos4210_power_off,
+ },
+ {
+ .label = "hsic0",
+ .id = EXYNOS4210_HSIC0,
+ .power_on = exynos4210_power_on,
+ .power_off = exynos4210_power_off,
+ },
+ {
+ .label = "hsic1",
+ .id = EXYNOS4210_HSIC1,
+ .power_on = exynos4210_power_on,
+ .power_off = exynos4210_power_off,
+ },
+};
+
+const struct samsung_usb2_phy_config exynos4210_usb2_phy_config = {
+ .has_mode_switch = 0,
+ .num_phys = EXYNOS4210_NUM_PHYS,
+ .phys = exynos4210_phys,
+ .rate_to_clk = exynos4210_rate_to_clk,
+};
diff --git a/drivers/phy/samsung/phy-exynos4x12-usb2.c b/drivers/phy/samsung/phy-exynos4x12-usb2.c
new file mode 100644
index 000000000..7f27a91ac
--- /dev/null
+++ b/drivers/phy/samsung/phy-exynos4x12-usb2.c
@@ -0,0 +1,378 @@
+/*
+ * Samsung SoC USB 1.1/2.0 PHY driver - Exynos 4x12 support
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ * Author: Kamil Debski <k.debski@samsung.com>
+ *
+ * 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/delay.h>
+#include <linux/io.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+#include "phy-samsung-usb2.h"
+
+/* Exynos USB PHY registers */
+
+/* PHY power control */
+#define EXYNOS_4x12_UPHYPWR 0x0
+
+#define EXYNOS_4x12_UPHYPWR_PHY0_SUSPEND BIT(0)
+#define EXYNOS_4x12_UPHYPWR_PHY0_PWR BIT(3)
+#define EXYNOS_4x12_UPHYPWR_PHY0_OTG_PWR BIT(4)
+#define EXYNOS_4x12_UPHYPWR_PHY0_SLEEP BIT(5)
+#define EXYNOS_4x12_UPHYPWR_PHY0 ( \
+ EXYNOS_4x12_UPHYPWR_PHY0_SUSPEND | \
+ EXYNOS_4x12_UPHYPWR_PHY0_PWR | \
+ EXYNOS_4x12_UPHYPWR_PHY0_OTG_PWR | \
+ EXYNOS_4x12_UPHYPWR_PHY0_SLEEP)
+
+#define EXYNOS_4x12_UPHYPWR_PHY1_SUSPEND BIT(6)
+#define EXYNOS_4x12_UPHYPWR_PHY1_PWR BIT(7)
+#define EXYNOS_4x12_UPHYPWR_PHY1_SLEEP BIT(8)
+#define EXYNOS_4x12_UPHYPWR_PHY1 ( \
+ EXYNOS_4x12_UPHYPWR_PHY1_SUSPEND | \
+ EXYNOS_4x12_UPHYPWR_PHY1_PWR | \
+ EXYNOS_4x12_UPHYPWR_PHY1_SLEEP)
+
+#define EXYNOS_4x12_UPHYPWR_HSIC0_SUSPEND BIT(9)
+#define EXYNOS_4x12_UPHYPWR_HSIC0_PWR BIT(10)
+#define EXYNOS_4x12_UPHYPWR_HSIC0_SLEEP BIT(11)
+#define EXYNOS_4x12_UPHYPWR_HSIC0 ( \
+ EXYNOS_4x12_UPHYPWR_HSIC0_SUSPEND | \
+ EXYNOS_4x12_UPHYPWR_HSIC0_PWR | \
+ EXYNOS_4x12_UPHYPWR_HSIC0_SLEEP)
+
+#define EXYNOS_4x12_UPHYPWR_HSIC1_SUSPEND BIT(12)
+#define EXYNOS_4x12_UPHYPWR_HSIC1_PWR BIT(13)
+#define EXYNOS_4x12_UPHYPWR_HSIC1_SLEEP BIT(14)
+#define EXYNOS_4x12_UPHYPWR_HSIC1 ( \
+ EXYNOS_4x12_UPHYPWR_HSIC1_SUSPEND | \
+ EXYNOS_4x12_UPHYPWR_HSIC1_PWR | \
+ EXYNOS_4x12_UPHYPWR_HSIC1_SLEEP)
+
+/* PHY clock control */
+#define EXYNOS_4x12_UPHYCLK 0x4
+
+#define EXYNOS_4x12_UPHYCLK_PHYFSEL_MASK (0x7 << 0)
+#define EXYNOS_4x12_UPHYCLK_PHYFSEL_OFFSET 0
+#define EXYNOS_4x12_UPHYCLK_PHYFSEL_9MHZ6 (0x0 << 0)
+#define EXYNOS_4x12_UPHYCLK_PHYFSEL_10MHZ (0x1 << 0)
+#define EXYNOS_4x12_UPHYCLK_PHYFSEL_12MHZ (0x2 << 0)
+#define EXYNOS_4x12_UPHYCLK_PHYFSEL_19MHZ2 (0x3 << 0)
+#define EXYNOS_4x12_UPHYCLK_PHYFSEL_20MHZ (0x4 << 0)
+#define EXYNOS_4x12_UPHYCLK_PHYFSEL_24MHZ (0x5 << 0)
+#define EXYNOS_4x12_UPHYCLK_PHYFSEL_50MHZ (0x7 << 0)
+
+#define EXYNOS_3250_UPHYCLK_REFCLKSEL (0x2 << 8)
+
+#define EXYNOS_4x12_UPHYCLK_PHY0_ID_PULLUP BIT(3)
+#define EXYNOS_4x12_UPHYCLK_PHY0_COMMON_ON BIT(4)
+#define EXYNOS_4x12_UPHYCLK_PHY1_COMMON_ON BIT(7)
+
+#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_MASK (0x7f << 10)
+#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_OFFSET 10
+#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_12MHZ (0x24 << 10)
+#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_15MHZ (0x1c << 10)
+#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_16MHZ (0x1a << 10)
+#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_19MHZ2 (0x15 << 10)
+#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_20MHZ (0x14 << 10)
+
+/* PHY reset control */
+#define EXYNOS_4x12_UPHYRST 0x8
+
+#define EXYNOS_4x12_URSTCON_PHY0 BIT(0)
+#define EXYNOS_4x12_URSTCON_OTG_HLINK BIT(1)
+#define EXYNOS_4x12_URSTCON_OTG_PHYLINK BIT(2)
+#define EXYNOS_4x12_URSTCON_HOST_PHY BIT(3)
+/* The following bit defines are presented in the
+ * order taken from the Exynos4412 reference manual.
+ *
+ * During experiments with the hardware and debugging
+ * it was determined that the hardware behaves contrary
+ * to the manual.
+ *
+ * The following bit values were chaned accordingly to the
+ * results of real hardware experiments.
+ */
+#define EXYNOS_4x12_URSTCON_PHY1 BIT(4)
+#define EXYNOS_4x12_URSTCON_HSIC0 BIT(6)
+#define EXYNOS_4x12_URSTCON_HSIC1 BIT(5)
+#define EXYNOS_4x12_URSTCON_HOST_LINK_ALL BIT(7)
+#define EXYNOS_4x12_URSTCON_HOST_LINK_P0 BIT(10)
+#define EXYNOS_4x12_URSTCON_HOST_LINK_P1 BIT(9)
+#define EXYNOS_4x12_URSTCON_HOST_LINK_P2 BIT(8)
+
+/* Isolation, configured in the power management unit */
+#define EXYNOS_4x12_USB_ISOL_OFFSET 0x704
+#define EXYNOS_4x12_USB_ISOL_OTG BIT(0)
+#define EXYNOS_4x12_USB_ISOL_HSIC0_OFFSET 0x708
+#define EXYNOS_4x12_USB_ISOL_HSIC0 BIT(0)
+#define EXYNOS_4x12_USB_ISOL_HSIC1_OFFSET 0x70c
+#define EXYNOS_4x12_USB_ISOL_HSIC1 BIT(0)
+
+/* Mode switching SUB Device <-> Host */
+#define EXYNOS_4x12_MODE_SWITCH_OFFSET 0x21c
+#define EXYNOS_4x12_MODE_SWITCH_MASK 1
+#define EXYNOS_4x12_MODE_SWITCH_DEVICE 0
+#define EXYNOS_4x12_MODE_SWITCH_HOST 1
+
+enum exynos4x12_phy_id {
+ EXYNOS4x12_DEVICE,
+ EXYNOS4x12_HOST,
+ EXYNOS4x12_HSIC0,
+ EXYNOS4x12_HSIC1,
+ EXYNOS4x12_NUM_PHYS,
+};
+
+/*
+ * exynos4x12_rate_to_clk() converts the supplied clock rate to the value that
+ * can be written to the phy register.
+ */
+static int exynos4x12_rate_to_clk(unsigned long rate, u32 *reg)
+{
+ /* EXYNOS_4x12_UPHYCLK_PHYFSEL_MASK */
+
+ switch (rate) {
+ case 9600 * KHZ:
+ *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_9MHZ6;
+ break;
+ case 10 * MHZ:
+ *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_10MHZ;
+ break;
+ case 12 * MHZ:
+ *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_12MHZ;
+ break;
+ case 19200 * KHZ:
+ *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_19MHZ2;
+ break;
+ case 20 * MHZ:
+ *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_20MHZ;
+ break;
+ case 24 * MHZ:
+ *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_24MHZ;
+ break;
+ case 50 * MHZ:
+ *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_50MHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void exynos4x12_isol(struct samsung_usb2_phy_instance *inst, bool on)
+{
+ struct samsung_usb2_phy_driver *drv = inst->drv;
+ u32 offset;
+ u32 mask;
+
+ switch (inst->cfg->id) {
+ case EXYNOS4x12_DEVICE:
+ case EXYNOS4x12_HOST:
+ offset = EXYNOS_4x12_USB_ISOL_OFFSET;
+ mask = EXYNOS_4x12_USB_ISOL_OTG;
+ break;
+ case EXYNOS4x12_HSIC0:
+ offset = EXYNOS_4x12_USB_ISOL_HSIC0_OFFSET;
+ mask = EXYNOS_4x12_USB_ISOL_HSIC0;
+ break;
+ case EXYNOS4x12_HSIC1:
+ offset = EXYNOS_4x12_USB_ISOL_HSIC1_OFFSET;
+ mask = EXYNOS_4x12_USB_ISOL_HSIC1;
+ break;
+ default:
+ return;
+ }
+
+ regmap_update_bits(drv->reg_pmu, offset, mask, on ? 0 : mask);
+}
+
+static void exynos4x12_setup_clk(struct samsung_usb2_phy_instance *inst)
+{
+ struct samsung_usb2_phy_driver *drv = inst->drv;
+ u32 clk;
+
+ clk = readl(drv->reg_phy + EXYNOS_4x12_UPHYCLK);
+ clk &= ~EXYNOS_4x12_UPHYCLK_PHYFSEL_MASK;
+
+ if (drv->cfg->has_refclk_sel)
+ clk = EXYNOS_3250_UPHYCLK_REFCLKSEL;
+
+ clk |= drv->ref_reg_val << EXYNOS_4x12_UPHYCLK_PHYFSEL_OFFSET;
+ clk |= EXYNOS_4x12_UPHYCLK_PHY1_COMMON_ON;
+ writel(clk, drv->reg_phy + EXYNOS_4x12_UPHYCLK);
+}
+
+static void exynos4x12_phy_pwr(struct samsung_usb2_phy_instance *inst, bool on)
+{
+ struct samsung_usb2_phy_driver *drv = inst->drv;
+ u32 rstbits = 0;
+ u32 phypwr = 0;
+ u32 rst;
+ u32 pwr;
+
+ switch (inst->cfg->id) {
+ case EXYNOS4x12_DEVICE:
+ phypwr = EXYNOS_4x12_UPHYPWR_PHY0;
+ rstbits = EXYNOS_4x12_URSTCON_PHY0;
+ break;
+ case EXYNOS4x12_HOST:
+ phypwr = EXYNOS_4x12_UPHYPWR_PHY1;
+ rstbits = EXYNOS_4x12_URSTCON_HOST_PHY |
+ EXYNOS_4x12_URSTCON_PHY1 |
+ EXYNOS_4x12_URSTCON_HOST_LINK_P0;
+ break;
+ case EXYNOS4x12_HSIC0:
+ phypwr = EXYNOS_4x12_UPHYPWR_HSIC0;
+ rstbits = EXYNOS_4x12_URSTCON_HSIC0 |
+ EXYNOS_4x12_URSTCON_HOST_LINK_P1;
+ break;
+ case EXYNOS4x12_HSIC1:
+ phypwr = EXYNOS_4x12_UPHYPWR_HSIC1;
+ rstbits = EXYNOS_4x12_URSTCON_HSIC1 |
+ EXYNOS_4x12_URSTCON_HOST_LINK_P1;
+ break;
+ }
+
+ if (on) {
+ pwr = readl(drv->reg_phy + EXYNOS_4x12_UPHYPWR);
+ pwr &= ~phypwr;
+ writel(pwr, drv->reg_phy + EXYNOS_4x12_UPHYPWR);
+
+ rst = readl(drv->reg_phy + EXYNOS_4x12_UPHYRST);
+ rst |= rstbits;
+ writel(rst, drv->reg_phy + EXYNOS_4x12_UPHYRST);
+ udelay(10);
+ rst &= ~rstbits;
+ writel(rst, drv->reg_phy + EXYNOS_4x12_UPHYRST);
+ /* The following delay is necessary for the reset sequence to be
+ * completed */
+ udelay(80);
+ } else {
+ pwr = readl(drv->reg_phy + EXYNOS_4x12_UPHYPWR);
+ pwr |= phypwr;
+ writel(pwr, drv->reg_phy + EXYNOS_4x12_UPHYPWR);
+ }
+}
+
+static void exynos4x12_power_on_int(struct samsung_usb2_phy_instance *inst)
+{
+ if (inst->int_cnt++ > 0)
+ return;
+
+ exynos4x12_setup_clk(inst);
+ exynos4x12_isol(inst, 0);
+ exynos4x12_phy_pwr(inst, 1);
+}
+
+static int exynos4x12_power_on(struct samsung_usb2_phy_instance *inst)
+{
+ struct samsung_usb2_phy_driver *drv = inst->drv;
+
+ if (inst->ext_cnt++ > 0)
+ return 0;
+
+ if (inst->cfg->id == EXYNOS4x12_HOST) {
+ regmap_update_bits(drv->reg_sys, EXYNOS_4x12_MODE_SWITCH_OFFSET,
+ EXYNOS_4x12_MODE_SWITCH_MASK,
+ EXYNOS_4x12_MODE_SWITCH_HOST);
+ exynos4x12_power_on_int(&drv->instances[EXYNOS4x12_DEVICE]);
+ }
+
+ if (inst->cfg->id == EXYNOS4x12_DEVICE && drv->cfg->has_mode_switch)
+ regmap_update_bits(drv->reg_sys, EXYNOS_4x12_MODE_SWITCH_OFFSET,
+ EXYNOS_4x12_MODE_SWITCH_MASK,
+ EXYNOS_4x12_MODE_SWITCH_DEVICE);
+
+ if (inst->cfg->id == EXYNOS4x12_HSIC0 ||
+ inst->cfg->id == EXYNOS4x12_HSIC1) {
+ exynos4x12_power_on_int(&drv->instances[EXYNOS4x12_DEVICE]);
+ exynos4x12_power_on_int(&drv->instances[EXYNOS4x12_HOST]);
+ }
+
+ exynos4x12_power_on_int(inst);
+
+ return 0;
+}
+
+static void exynos4x12_power_off_int(struct samsung_usb2_phy_instance *inst)
+{
+ if (inst->int_cnt-- > 1)
+ return;
+
+ exynos4x12_isol(inst, 1);
+ exynos4x12_phy_pwr(inst, 0);
+}
+
+static int exynos4x12_power_off(struct samsung_usb2_phy_instance *inst)
+{
+ struct samsung_usb2_phy_driver *drv = inst->drv;
+
+ if (inst->ext_cnt-- > 1)
+ return 0;
+
+ if (inst->cfg->id == EXYNOS4x12_DEVICE && drv->cfg->has_mode_switch)
+ regmap_update_bits(drv->reg_sys, EXYNOS_4x12_MODE_SWITCH_OFFSET,
+ EXYNOS_4x12_MODE_SWITCH_MASK,
+ EXYNOS_4x12_MODE_SWITCH_HOST);
+
+ if (inst->cfg->id == EXYNOS4x12_HOST)
+ exynos4x12_power_off_int(&drv->instances[EXYNOS4x12_DEVICE]);
+
+ if (inst->cfg->id == EXYNOS4x12_HSIC0 ||
+ inst->cfg->id == EXYNOS4x12_HSIC1) {
+ exynos4x12_power_off_int(&drv->instances[EXYNOS4x12_DEVICE]);
+ exynos4x12_power_off_int(&drv->instances[EXYNOS4x12_HOST]);
+ }
+
+ exynos4x12_power_off_int(inst);
+
+ return 0;
+}
+
+
+static const struct samsung_usb2_common_phy exynos4x12_phys[] = {
+ {
+ .label = "device",
+ .id = EXYNOS4x12_DEVICE,
+ .power_on = exynos4x12_power_on,
+ .power_off = exynos4x12_power_off,
+ },
+ {
+ .label = "host",
+ .id = EXYNOS4x12_HOST,
+ .power_on = exynos4x12_power_on,
+ .power_off = exynos4x12_power_off,
+ },
+ {
+ .label = "hsic0",
+ .id = EXYNOS4x12_HSIC0,
+ .power_on = exynos4x12_power_on,
+ .power_off = exynos4x12_power_off,
+ },
+ {
+ .label = "hsic1",
+ .id = EXYNOS4x12_HSIC1,
+ .power_on = exynos4x12_power_on,
+ .power_off = exynos4x12_power_off,
+ },
+};
+
+const struct samsung_usb2_phy_config exynos3250_usb2_phy_config = {
+ .has_refclk_sel = 1,
+ .num_phys = 1,
+ .phys = exynos4x12_phys,
+ .rate_to_clk = exynos4x12_rate_to_clk,
+};
+
+const struct samsung_usb2_phy_config exynos4x12_usb2_phy_config = {
+ .has_mode_switch = 1,
+ .num_phys = EXYNOS4x12_NUM_PHYS,
+ .phys = exynos4x12_phys,
+ .rate_to_clk = exynos4x12_rate_to_clk,
+};
diff --git a/drivers/phy/samsung/phy-exynos5-usbdrd.c b/drivers/phy/samsung/phy-exynos5-usbdrd.c
new file mode 100644
index 000000000..1feb1e1bf
--- /dev/null
+++ b/drivers/phy/samsung/phy-exynos5-usbdrd.c
@@ -0,0 +1,968 @@
+/*
+ * Samsung EXYNOS5 SoC series USB DRD PHY driver
+ *
+ * Phy provider for USB 3.0 DRD controller on Exynos5 SoC series
+ *
+ * Copyright (C) 2014 Samsung Electronics Co., Ltd.
+ * Author: Vivek Gautam <gautam.vivek@samsung.com>
+ *
+ * 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/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/soc/samsung/exynos-regs-pmu.h>
+
+/* Exynos USB PHY registers */
+#define EXYNOS5_FSEL_9MHZ6 0x0
+#define EXYNOS5_FSEL_10MHZ 0x1
+#define EXYNOS5_FSEL_12MHZ 0x2
+#define EXYNOS5_FSEL_19MHZ2 0x3
+#define EXYNOS5_FSEL_20MHZ 0x4
+#define EXYNOS5_FSEL_24MHZ 0x5
+#define EXYNOS5_FSEL_50MHZ 0x7
+
+/* EXYNOS5: USB 3.0 DRD PHY registers */
+#define EXYNOS5_DRD_LINKSYSTEM 0x04
+
+#define LINKSYSTEM_FLADJ_MASK (0x3f << 1)
+#define LINKSYSTEM_FLADJ(_x) ((_x) << 1)
+#define LINKSYSTEM_XHCI_VERSION_CONTROL BIT(27)
+
+#define EXYNOS5_DRD_PHYUTMI 0x08
+
+#define PHYUTMI_OTGDISABLE BIT(6)
+#define PHYUTMI_FORCESUSPEND BIT(1)
+#define PHYUTMI_FORCESLEEP BIT(0)
+
+#define EXYNOS5_DRD_PHYPIPE 0x0c
+
+#define EXYNOS5_DRD_PHYCLKRST 0x10
+
+#define PHYCLKRST_EN_UTMISUSPEND BIT(31)
+
+#define PHYCLKRST_SSC_REFCLKSEL_MASK (0xff << 23)
+#define PHYCLKRST_SSC_REFCLKSEL(_x) ((_x) << 23)
+
+#define PHYCLKRST_SSC_RANGE_MASK (0x03 << 21)
+#define PHYCLKRST_SSC_RANGE(_x) ((_x) << 21)
+
+#define PHYCLKRST_SSC_EN BIT(20)
+#define PHYCLKRST_REF_SSP_EN BIT(19)
+#define PHYCLKRST_REF_CLKDIV2 BIT(18)
+
+#define PHYCLKRST_MPLL_MULTIPLIER_MASK (0x7f << 11)
+#define PHYCLKRST_MPLL_MULTIPLIER_100MHZ_REF (0x19 << 11)
+#define PHYCLKRST_MPLL_MULTIPLIER_50M_REF (0x32 << 11)
+#define PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF (0x68 << 11)
+#define PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF (0x7d << 11)
+#define PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF (0x02 << 11)
+
+#define PHYCLKRST_FSEL_UTMI_MASK (0x7 << 5)
+#define PHYCLKRST_FSEL_PIPE_MASK (0x7 << 8)
+#define PHYCLKRST_FSEL(_x) ((_x) << 5)
+#define PHYCLKRST_FSEL_PAD_100MHZ (0x27 << 5)
+#define PHYCLKRST_FSEL_PAD_24MHZ (0x2a << 5)
+#define PHYCLKRST_FSEL_PAD_20MHZ (0x31 << 5)
+#define PHYCLKRST_FSEL_PAD_19_2MHZ (0x38 << 5)
+
+#define PHYCLKRST_RETENABLEN BIT(4)
+
+#define PHYCLKRST_REFCLKSEL_MASK (0x03 << 2)
+#define PHYCLKRST_REFCLKSEL_PAD_REFCLK (0x2 << 2)
+#define PHYCLKRST_REFCLKSEL_EXT_REFCLK (0x3 << 2)
+
+#define PHYCLKRST_PORTRESET BIT(1)
+#define PHYCLKRST_COMMONONN BIT(0)
+
+#define EXYNOS5_DRD_PHYREG0 0x14
+#define PHYREG0_SSC_REF_CLK_SEL BIT(21)
+#define PHYREG0_SSC_RANGE BIT(20)
+#define PHYREG0_CR_WRITE BIT(19)
+#define PHYREG0_CR_READ BIT(18)
+#define PHYREG0_CR_DATA_IN(_x) ((_x) << 2)
+#define PHYREG0_CR_CAP_DATA BIT(1)
+#define PHYREG0_CR_CAP_ADDR BIT(0)
+
+#define EXYNOS5_DRD_PHYREG1 0x18
+#define PHYREG1_CR_DATA_OUT(_x) ((_x) << 1)
+#define PHYREG1_CR_ACK BIT(0)
+
+#define EXYNOS5_DRD_PHYPARAM0 0x1c
+
+#define PHYPARAM0_REF_USE_PAD BIT(31)
+#define PHYPARAM0_REF_LOSLEVEL_MASK (0x1f << 26)
+#define PHYPARAM0_REF_LOSLEVEL (0x9 << 26)
+
+#define EXYNOS5_DRD_PHYPARAM1 0x20
+
+#define PHYPARAM1_PCS_TXDEEMPH_MASK (0x1f << 0)
+#define PHYPARAM1_PCS_TXDEEMPH (0x1c)
+
+#define EXYNOS5_DRD_PHYTERM 0x24
+
+#define EXYNOS5_DRD_PHYTEST 0x28
+
+#define PHYTEST_POWERDOWN_SSP BIT(3)
+#define PHYTEST_POWERDOWN_HSP BIT(2)
+
+#define EXYNOS5_DRD_PHYADP 0x2c
+
+#define EXYNOS5_DRD_PHYUTMICLKSEL 0x30
+
+#define PHYUTMICLKSEL_UTMI_CLKSEL BIT(2)
+
+#define EXYNOS5_DRD_PHYRESUME 0x34
+#define EXYNOS5_DRD_LINKPORT 0x44
+
+/* USB 3.0 DRD PHY SS Function Control Reg; accessed by CR_PORT */
+#define EXYNOS5_DRD_PHYSS_LOSLEVEL_OVRD_IN (0x15)
+#define LOSLEVEL_OVRD_IN_LOS_BIAS_5420 (0x5 << 13)
+#define LOSLEVEL_OVRD_IN_LOS_BIAS_DEFAULT (0x0 << 13)
+#define LOSLEVEL_OVRD_IN_EN (0x1 << 10)
+#define LOSLEVEL_OVRD_IN_LOS_LEVEL_DEFAULT (0x9 << 0)
+
+#define EXYNOS5_DRD_PHYSS_TX_VBOOSTLEVEL_OVRD_IN (0x12)
+#define TX_VBOOSTLEVEL_OVRD_IN_VBOOST_5420 (0x5 << 13)
+#define TX_VBOOSTLEVEL_OVRD_IN_VBOOST_DEFAULT (0x4 << 13)
+
+#define EXYNOS5_DRD_PHYSS_LANE0_TX_DEBUG (0x1010)
+#define LANE0_TX_DEBUG_RXDET_MEAS_TIME_19M2_20M (0x4 << 4)
+#define LANE0_TX_DEBUG_RXDET_MEAS_TIME_24M (0x8 << 4)
+#define LANE0_TX_DEBUG_RXDET_MEAS_TIME_25M_26M (0x8 << 4)
+#define LANE0_TX_DEBUG_RXDET_MEAS_TIME_48M_50M_52M (0x20 << 4)
+#define LANE0_TX_DEBUG_RXDET_MEAS_TIME_62M5 (0x20 << 4)
+#define LANE0_TX_DEBUG_RXDET_MEAS_TIME_96M_100M (0x40 << 4)
+
+#define KHZ 1000
+#define MHZ (KHZ * KHZ)
+
+enum exynos5_usbdrd_phy_id {
+ EXYNOS5_DRDPHY_UTMI,
+ EXYNOS5_DRDPHY_PIPE3,
+ EXYNOS5_DRDPHYS_NUM,
+};
+
+struct phy_usb_instance;
+struct exynos5_usbdrd_phy;
+
+struct exynos5_usbdrd_phy_config {
+ u32 id;
+ void (*phy_isol)(struct phy_usb_instance *inst, u32 on);
+ void (*phy_init)(struct exynos5_usbdrd_phy *phy_drd);
+ unsigned int (*set_refclk)(struct phy_usb_instance *inst);
+};
+
+struct exynos5_usbdrd_phy_drvdata {
+ const struct exynos5_usbdrd_phy_config *phy_cfg;
+ u32 pmu_offset_usbdrd0_phy;
+ u32 pmu_offset_usbdrd1_phy;
+ bool has_common_clk_gate;
+};
+
+/**
+ * struct exynos5_usbdrd_phy - driver data for USB 3.0 PHY
+ * @dev: pointer to device instance of this platform device
+ * @reg_phy: usb phy controller register memory base
+ * @clk: phy clock for register access
+ * @pipeclk: clock for pipe3 phy
+ * @utmiclk: clock for utmi+ phy
+ * @itpclk: clock for ITP generation
+ * @drv_data: pointer to SoC level driver data structure
+ * @phys[]: array for 'EXYNOS5_DRDPHYS_NUM' number of PHY
+ * instances each with its 'phy' and 'phy_cfg'.
+ * @extrefclk: frequency select settings when using 'separate
+ * reference clocks' for SS and HS operations
+ * @ref_clk: reference clock to PHY block from which PHY's
+ * operational clocks are derived
+ * vbus: VBUS regulator for phy
+ * vbus_boost: Boost regulator for VBUS present on few Exynos boards
+ */
+struct exynos5_usbdrd_phy {
+ struct device *dev;
+ void __iomem *reg_phy;
+ struct clk *clk;
+ struct clk *pipeclk;
+ struct clk *utmiclk;
+ struct clk *itpclk;
+ const struct exynos5_usbdrd_phy_drvdata *drv_data;
+ struct phy_usb_instance {
+ struct phy *phy;
+ u32 index;
+ struct regmap *reg_pmu;
+ u32 pmu_offset;
+ const struct exynos5_usbdrd_phy_config *phy_cfg;
+ } phys[EXYNOS5_DRDPHYS_NUM];
+ u32 extrefclk;
+ struct clk *ref_clk;
+ struct regulator *vbus;
+ struct regulator *vbus_boost;
+};
+
+static inline
+struct exynos5_usbdrd_phy *to_usbdrd_phy(struct phy_usb_instance *inst)
+{
+ return container_of((inst), struct exynos5_usbdrd_phy,
+ phys[(inst)->index]);
+}
+
+/*
+ * exynos5_rate_to_clk() converts the supplied clock rate to the value that
+ * can be written to the phy register.
+ */
+static unsigned int exynos5_rate_to_clk(unsigned long rate, u32 *reg)
+{
+ /* EXYNOS5_FSEL_MASK */
+
+ switch (rate) {
+ case 9600 * KHZ:
+ *reg = EXYNOS5_FSEL_9MHZ6;
+ break;
+ case 10 * MHZ:
+ *reg = EXYNOS5_FSEL_10MHZ;
+ break;
+ case 12 * MHZ:
+ *reg = EXYNOS5_FSEL_12MHZ;
+ break;
+ case 19200 * KHZ:
+ *reg = EXYNOS5_FSEL_19MHZ2;
+ break;
+ case 20 * MHZ:
+ *reg = EXYNOS5_FSEL_20MHZ;
+ break;
+ case 24 * MHZ:
+ *reg = EXYNOS5_FSEL_24MHZ;
+ break;
+ case 50 * MHZ:
+ *reg = EXYNOS5_FSEL_50MHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void exynos5_usbdrd_phy_isol(struct phy_usb_instance *inst,
+ unsigned int on)
+{
+ unsigned int val;
+
+ if (!inst->reg_pmu)
+ return;
+
+ val = on ? 0 : EXYNOS4_PHY_ENABLE;
+
+ regmap_update_bits(inst->reg_pmu, inst->pmu_offset,
+ EXYNOS4_PHY_ENABLE, val);
+}
+
+/*
+ * Sets the pipe3 phy's clk as EXTREFCLK (XXTI) which is internal clock
+ * from clock core. Further sets multiplier values and spread spectrum
+ * clock settings for SuperSpeed operations.
+ */
+static unsigned int
+exynos5_usbdrd_pipe3_set_refclk(struct phy_usb_instance *inst)
+{
+ u32 reg;
+ struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst);
+
+ /* restore any previous reference clock settings */
+ reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST);
+
+ /* Use EXTREFCLK as ref clock */
+ reg &= ~PHYCLKRST_REFCLKSEL_MASK;
+ reg |= PHYCLKRST_REFCLKSEL_EXT_REFCLK;
+
+ /* FSEL settings corresponding to reference clock */
+ reg &= ~PHYCLKRST_FSEL_PIPE_MASK |
+ PHYCLKRST_MPLL_MULTIPLIER_MASK |
+ PHYCLKRST_SSC_REFCLKSEL_MASK;
+ switch (phy_drd->extrefclk) {
+ case EXYNOS5_FSEL_50MHZ:
+ reg |= (PHYCLKRST_MPLL_MULTIPLIER_50M_REF |
+ PHYCLKRST_SSC_REFCLKSEL(0x00));
+ break;
+ case EXYNOS5_FSEL_24MHZ:
+ reg |= (PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF |
+ PHYCLKRST_SSC_REFCLKSEL(0x88));
+ break;
+ case EXYNOS5_FSEL_20MHZ:
+ reg |= (PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF |
+ PHYCLKRST_SSC_REFCLKSEL(0x00));
+ break;
+ case EXYNOS5_FSEL_19MHZ2:
+ reg |= (PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF |
+ PHYCLKRST_SSC_REFCLKSEL(0x88));
+ break;
+ default:
+ dev_dbg(phy_drd->dev, "unsupported ref clk\n");
+ break;
+ }
+
+ return reg;
+}
+
+/*
+ * Sets the utmi phy's clk as EXTREFCLK (XXTI) which is internal clock
+ * from clock core. Further sets the FSEL values for HighSpeed operations.
+ */
+static unsigned int
+exynos5_usbdrd_utmi_set_refclk(struct phy_usb_instance *inst)
+{
+ u32 reg;
+ struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst);
+
+ /* restore any previous reference clock settings */
+ reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST);
+
+ reg &= ~PHYCLKRST_REFCLKSEL_MASK;
+ reg |= PHYCLKRST_REFCLKSEL_EXT_REFCLK;
+
+ reg &= ~PHYCLKRST_FSEL_UTMI_MASK |
+ PHYCLKRST_MPLL_MULTIPLIER_MASK |
+ PHYCLKRST_SSC_REFCLKSEL_MASK;
+ reg |= PHYCLKRST_FSEL(phy_drd->extrefclk);
+
+ return reg;
+}
+
+static void exynos5_usbdrd_pipe3_init(struct exynos5_usbdrd_phy *phy_drd)
+{
+ u32 reg;
+
+ reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1);
+ /* Set Tx De-Emphasis level */
+ reg &= ~PHYPARAM1_PCS_TXDEEMPH_MASK;
+ reg |= PHYPARAM1_PCS_TXDEEMPH;
+ writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1);
+
+ reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST);
+ reg &= ~PHYTEST_POWERDOWN_SSP;
+ writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST);
+}
+
+static void exynos5_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd)
+{
+ u32 reg;
+
+ reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0);
+ /* Set Loss-of-Signal Detector sensitivity */
+ reg &= ~PHYPARAM0_REF_LOSLEVEL_MASK;
+ reg |= PHYPARAM0_REF_LOSLEVEL;
+ writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0);
+
+ reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1);
+ /* Set Tx De-Emphasis level */
+ reg &= ~PHYPARAM1_PCS_TXDEEMPH_MASK;
+ reg |= PHYPARAM1_PCS_TXDEEMPH;
+ writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1);
+
+ /* UTMI Power Control */
+ writel(PHYUTMI_OTGDISABLE, phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMI);
+
+ reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST);
+ reg &= ~PHYTEST_POWERDOWN_HSP;
+ writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST);
+}
+
+static int exynos5_usbdrd_phy_init(struct phy *phy)
+{
+ int ret;
+ u32 reg;
+ struct phy_usb_instance *inst = phy_get_drvdata(phy);
+ struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst);
+
+ ret = clk_prepare_enable(phy_drd->clk);
+ if (ret)
+ return ret;
+
+ /* Reset USB 3.0 PHY */
+ writel(0x0, phy_drd->reg_phy + EXYNOS5_DRD_PHYREG0);
+ writel(0x0, phy_drd->reg_phy + EXYNOS5_DRD_PHYRESUME);
+
+ /*
+ * Setting the Frame length Adj value[6:1] to default 0x20
+ * See xHCI 1.0 spec, 5.2.4
+ */
+ reg = LINKSYSTEM_XHCI_VERSION_CONTROL |
+ LINKSYSTEM_FLADJ(0x20);
+ writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_LINKSYSTEM);
+
+ reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0);
+ /* Select PHY CLK source */
+ reg &= ~PHYPARAM0_REF_USE_PAD;
+ writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0);
+
+ /* This bit must be set for both HS and SS operations */
+ reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMICLKSEL);
+ reg |= PHYUTMICLKSEL_UTMI_CLKSEL;
+ writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMICLKSEL);
+
+ /* UTMI or PIPE3 specific init */
+ inst->phy_cfg->phy_init(phy_drd);
+
+ /* reference clock settings */
+ reg = inst->phy_cfg->set_refclk(inst);
+
+ /* Digital power supply in normal operating mode */
+ reg |= PHYCLKRST_RETENABLEN |
+ /* Enable ref clock for SS function */
+ PHYCLKRST_REF_SSP_EN |
+ /* Enable spread spectrum */
+ PHYCLKRST_SSC_EN |
+ /* Power down HS Bias and PLL blocks in suspend mode */
+ PHYCLKRST_COMMONONN |
+ /* Reset the port */
+ PHYCLKRST_PORTRESET;
+
+ writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST);
+
+ udelay(10);
+
+ reg &= ~PHYCLKRST_PORTRESET;
+ writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST);
+
+ clk_disable_unprepare(phy_drd->clk);
+
+ return 0;
+}
+
+static int exynos5_usbdrd_phy_exit(struct phy *phy)
+{
+ int ret;
+ u32 reg;
+ struct phy_usb_instance *inst = phy_get_drvdata(phy);
+ struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst);
+
+ ret = clk_prepare_enable(phy_drd->clk);
+ if (ret)
+ return ret;
+
+ reg = PHYUTMI_OTGDISABLE |
+ PHYUTMI_FORCESUSPEND |
+ PHYUTMI_FORCESLEEP;
+ writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMI);
+
+ /* Resetting the PHYCLKRST enable bits to reduce leakage current */
+ reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST);
+ reg &= ~(PHYCLKRST_REF_SSP_EN |
+ PHYCLKRST_SSC_EN |
+ PHYCLKRST_COMMONONN);
+ writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST);
+
+ /* Control PHYTEST to remove leakage current */
+ reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST);
+ reg |= PHYTEST_POWERDOWN_SSP |
+ PHYTEST_POWERDOWN_HSP;
+ writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST);
+
+ clk_disable_unprepare(phy_drd->clk);
+
+ return 0;
+}
+
+static int exynos5_usbdrd_phy_power_on(struct phy *phy)
+{
+ int ret;
+ struct phy_usb_instance *inst = phy_get_drvdata(phy);
+ struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst);
+
+ dev_dbg(phy_drd->dev, "Request to power_on usbdrd_phy phy\n");
+
+ clk_prepare_enable(phy_drd->ref_clk);
+ if (!phy_drd->drv_data->has_common_clk_gate) {
+ clk_prepare_enable(phy_drd->pipeclk);
+ clk_prepare_enable(phy_drd->utmiclk);
+ clk_prepare_enable(phy_drd->itpclk);
+ }
+
+ /* Enable VBUS supply */
+ if (phy_drd->vbus_boost) {
+ ret = regulator_enable(phy_drd->vbus_boost);
+ if (ret) {
+ dev_err(phy_drd->dev,
+ "Failed to enable VBUS boost supply\n");
+ goto fail_vbus;
+ }
+ }
+
+ if (phy_drd->vbus) {
+ ret = regulator_enable(phy_drd->vbus);
+ if (ret) {
+ dev_err(phy_drd->dev, "Failed to enable VBUS supply\n");
+ goto fail_vbus_boost;
+ }
+ }
+
+ /* Power-on PHY*/
+ inst->phy_cfg->phy_isol(inst, 0);
+
+ return 0;
+
+fail_vbus_boost:
+ if (phy_drd->vbus_boost)
+ regulator_disable(phy_drd->vbus_boost);
+
+fail_vbus:
+ clk_disable_unprepare(phy_drd->ref_clk);
+ if (!phy_drd->drv_data->has_common_clk_gate) {
+ clk_disable_unprepare(phy_drd->itpclk);
+ clk_disable_unprepare(phy_drd->utmiclk);
+ clk_disable_unprepare(phy_drd->pipeclk);
+ }
+
+ return ret;
+}
+
+static int exynos5_usbdrd_phy_power_off(struct phy *phy)
+{
+ struct phy_usb_instance *inst = phy_get_drvdata(phy);
+ struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst);
+
+ dev_dbg(phy_drd->dev, "Request to power_off usbdrd_phy phy\n");
+
+ /* Power-off the PHY */
+ inst->phy_cfg->phy_isol(inst, 1);
+
+ /* Disable VBUS supply */
+ if (phy_drd->vbus)
+ regulator_disable(phy_drd->vbus);
+ if (phy_drd->vbus_boost)
+ regulator_disable(phy_drd->vbus_boost);
+
+ clk_disable_unprepare(phy_drd->ref_clk);
+ if (!phy_drd->drv_data->has_common_clk_gate) {
+ clk_disable_unprepare(phy_drd->itpclk);
+ clk_disable_unprepare(phy_drd->pipeclk);
+ clk_disable_unprepare(phy_drd->utmiclk);
+ }
+
+ return 0;
+}
+
+static int crport_handshake(struct exynos5_usbdrd_phy *phy_drd,
+ u32 val, u32 cmd)
+{
+ u32 usec = 100;
+ unsigned int result;
+
+ writel(val | cmd, phy_drd->reg_phy + EXYNOS5_DRD_PHYREG0);
+
+ do {
+ result = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYREG1);
+ if (result & PHYREG1_CR_ACK)
+ break;
+
+ udelay(1);
+ } while (usec-- > 0);
+
+ if (!usec) {
+ dev_err(phy_drd->dev,
+ "CRPORT handshake timeout1 (0x%08x)\n", val);
+ return -ETIME;
+ }
+
+ usec = 100;
+
+ writel(val, phy_drd->reg_phy + EXYNOS5_DRD_PHYREG0);
+
+ do {
+ result = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYREG1);
+ if (!(result & PHYREG1_CR_ACK))
+ break;
+
+ udelay(1);
+ } while (usec-- > 0);
+
+ if (!usec) {
+ dev_err(phy_drd->dev,
+ "CRPORT handshake timeout2 (0x%08x)\n", val);
+ return -ETIME;
+ }
+
+ return 0;
+}
+
+static int crport_ctrl_write(struct exynos5_usbdrd_phy *phy_drd,
+ u32 addr, u32 data)
+{
+ int ret;
+
+ /* Write Address */
+ writel(PHYREG0_CR_DATA_IN(addr),
+ phy_drd->reg_phy + EXYNOS5_DRD_PHYREG0);
+ ret = crport_handshake(phy_drd, PHYREG0_CR_DATA_IN(addr),
+ PHYREG0_CR_CAP_ADDR);
+ if (ret)
+ return ret;
+
+ /* Write Data */
+ writel(PHYREG0_CR_DATA_IN(data),
+ phy_drd->reg_phy + EXYNOS5_DRD_PHYREG0);
+ ret = crport_handshake(phy_drd, PHYREG0_CR_DATA_IN(data),
+ PHYREG0_CR_CAP_DATA);
+ if (ret)
+ return ret;
+
+ ret = crport_handshake(phy_drd, PHYREG0_CR_DATA_IN(data),
+ PHYREG0_CR_WRITE);
+
+ return ret;
+}
+
+/*
+ * Calibrate few PHY parameters using CR_PORT register to meet
+ * SuperSpeed requirements on Exynos5420 and Exynos5800 systems,
+ * which have 28nm USB 3.0 DRD PHY.
+ */
+static int exynos5420_usbdrd_phy_calibrate(struct exynos5_usbdrd_phy *phy_drd)
+{
+ unsigned int temp;
+ int ret = 0;
+
+ /*
+ * Change los_bias to (0x5) for 28nm PHY from a
+ * default value (0x0); los_level is set as default
+ * (0x9) as also reflected in los_level[30:26] bits
+ * of PHYPARAM0 register.
+ */
+ temp = LOSLEVEL_OVRD_IN_LOS_BIAS_5420 |
+ LOSLEVEL_OVRD_IN_EN |
+ LOSLEVEL_OVRD_IN_LOS_LEVEL_DEFAULT;
+ ret = crport_ctrl_write(phy_drd,
+ EXYNOS5_DRD_PHYSS_LOSLEVEL_OVRD_IN,
+ temp);
+ if (ret) {
+ dev_err(phy_drd->dev,
+ "Failed setting Loss-of-Signal level for SuperSpeed\n");
+ return ret;
+ }
+
+ /*
+ * Set tx_vboost_lvl to (0x5) for 28nm PHY Tuning,
+ * to raise Tx signal level from its default value of (0x4)
+ */
+ temp = TX_VBOOSTLEVEL_OVRD_IN_VBOOST_5420;
+ ret = crport_ctrl_write(phy_drd,
+ EXYNOS5_DRD_PHYSS_TX_VBOOSTLEVEL_OVRD_IN,
+ temp);
+ if (ret) {
+ dev_err(phy_drd->dev,
+ "Failed setting Tx-Vboost-Level for SuperSpeed\n");
+ return ret;
+ }
+
+ /*
+ * Set proper time to wait for RxDetect measurement, for
+ * desired reference clock of PHY, by tuning the CR_PORT
+ * register LANE0.TX_DEBUG which is internal to PHY.
+ * This fixes issue with few USB 3.0 devices, which are
+ * not detected (not even generate interrupts on the bus
+ * on insertion) without this change.
+ * e.g. Samsung SUM-TSB16S 3.0 USB drive.
+ */
+ switch (phy_drd->extrefclk) {
+ case EXYNOS5_FSEL_50MHZ:
+ temp = LANE0_TX_DEBUG_RXDET_MEAS_TIME_48M_50M_52M;
+ break;
+ case EXYNOS5_FSEL_20MHZ:
+ case EXYNOS5_FSEL_19MHZ2:
+ temp = LANE0_TX_DEBUG_RXDET_MEAS_TIME_19M2_20M;
+ break;
+ case EXYNOS5_FSEL_24MHZ:
+ default:
+ temp = LANE0_TX_DEBUG_RXDET_MEAS_TIME_24M;
+ break;
+ }
+
+ ret = crport_ctrl_write(phy_drd,
+ EXYNOS5_DRD_PHYSS_LANE0_TX_DEBUG,
+ temp);
+ if (ret)
+ dev_err(phy_drd->dev,
+ "Fail to set RxDet measurement time for SuperSpeed\n");
+
+ return ret;
+}
+
+static struct phy *exynos5_usbdrd_phy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct exynos5_usbdrd_phy *phy_drd = dev_get_drvdata(dev);
+
+ if (WARN_ON(args->args[0] >= EXYNOS5_DRDPHYS_NUM))
+ return ERR_PTR(-ENODEV);
+
+ return phy_drd->phys[args->args[0]].phy;
+}
+
+static int exynos5_usbdrd_phy_calibrate(struct phy *phy)
+{
+ struct phy_usb_instance *inst = phy_get_drvdata(phy);
+ struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst);
+
+ if (inst->phy_cfg->id == EXYNOS5_DRDPHY_UTMI)
+ return exynos5420_usbdrd_phy_calibrate(phy_drd);
+ return 0;
+}
+
+static const struct phy_ops exynos5_usbdrd_phy_ops = {
+ .init = exynos5_usbdrd_phy_init,
+ .exit = exynos5_usbdrd_phy_exit,
+ .power_on = exynos5_usbdrd_phy_power_on,
+ .power_off = exynos5_usbdrd_phy_power_off,
+ .calibrate = exynos5_usbdrd_phy_calibrate,
+ .owner = THIS_MODULE,
+};
+
+static int exynos5_usbdrd_phy_clk_handle(struct exynos5_usbdrd_phy *phy_drd)
+{
+ unsigned long ref_rate;
+ int ret;
+
+ phy_drd->clk = devm_clk_get(phy_drd->dev, "phy");
+ if (IS_ERR(phy_drd->clk)) {
+ dev_err(phy_drd->dev, "Failed to get phy clock\n");
+ return PTR_ERR(phy_drd->clk);
+ }
+
+ phy_drd->ref_clk = devm_clk_get(phy_drd->dev, "ref");
+ if (IS_ERR(phy_drd->ref_clk)) {
+ dev_err(phy_drd->dev, "Failed to get phy reference clock\n");
+ return PTR_ERR(phy_drd->ref_clk);
+ }
+ ref_rate = clk_get_rate(phy_drd->ref_clk);
+
+ ret = exynos5_rate_to_clk(ref_rate, &phy_drd->extrefclk);
+ if (ret) {
+ dev_err(phy_drd->dev, "Clock rate (%ld) not supported\n",
+ ref_rate);
+ return ret;
+ }
+
+ if (!phy_drd->drv_data->has_common_clk_gate) {
+ phy_drd->pipeclk = devm_clk_get(phy_drd->dev, "phy_pipe");
+ if (IS_ERR(phy_drd->pipeclk)) {
+ dev_info(phy_drd->dev,
+ "PIPE3 phy operational clock not specified\n");
+ phy_drd->pipeclk = NULL;
+ }
+
+ phy_drd->utmiclk = devm_clk_get(phy_drd->dev, "phy_utmi");
+ if (IS_ERR(phy_drd->utmiclk)) {
+ dev_info(phy_drd->dev,
+ "UTMI phy operational clock not specified\n");
+ phy_drd->utmiclk = NULL;
+ }
+
+ phy_drd->itpclk = devm_clk_get(phy_drd->dev, "itp");
+ if (IS_ERR(phy_drd->itpclk)) {
+ dev_info(phy_drd->dev,
+ "ITP clock from main OSC not specified\n");
+ phy_drd->itpclk = NULL;
+ }
+ }
+
+ return 0;
+}
+
+static const struct exynos5_usbdrd_phy_config phy_cfg_exynos5[] = {
+ {
+ .id = EXYNOS5_DRDPHY_UTMI,
+ .phy_isol = exynos5_usbdrd_phy_isol,
+ .phy_init = exynos5_usbdrd_utmi_init,
+ .set_refclk = exynos5_usbdrd_utmi_set_refclk,
+ },
+ {
+ .id = EXYNOS5_DRDPHY_PIPE3,
+ .phy_isol = exynos5_usbdrd_phy_isol,
+ .phy_init = exynos5_usbdrd_pipe3_init,
+ .set_refclk = exynos5_usbdrd_pipe3_set_refclk,
+ },
+};
+
+static const struct exynos5_usbdrd_phy_drvdata exynos5420_usbdrd_phy = {
+ .phy_cfg = phy_cfg_exynos5,
+ .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL,
+ .pmu_offset_usbdrd1_phy = EXYNOS5420_USBDRD1_PHY_CONTROL,
+ .has_common_clk_gate = true,
+};
+
+static const struct exynos5_usbdrd_phy_drvdata exynos5250_usbdrd_phy = {
+ .phy_cfg = phy_cfg_exynos5,
+ .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL,
+ .has_common_clk_gate = true,
+};
+
+static const struct exynos5_usbdrd_phy_drvdata exynos5433_usbdrd_phy = {
+ .phy_cfg = phy_cfg_exynos5,
+ .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL,
+ .pmu_offset_usbdrd1_phy = EXYNOS5433_USBHOST30_PHY_CONTROL,
+ .has_common_clk_gate = false,
+};
+
+static const struct exynos5_usbdrd_phy_drvdata exynos7_usbdrd_phy = {
+ .phy_cfg = phy_cfg_exynos5,
+ .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL,
+ .has_common_clk_gate = false,
+};
+
+static const struct of_device_id exynos5_usbdrd_phy_of_match[] = {
+ {
+ .compatible = "samsung,exynos5250-usbdrd-phy",
+ .data = &exynos5250_usbdrd_phy
+ }, {
+ .compatible = "samsung,exynos5420-usbdrd-phy",
+ .data = &exynos5420_usbdrd_phy
+ }, {
+ .compatible = "samsung,exynos5433-usbdrd-phy",
+ .data = &exynos5433_usbdrd_phy
+ }, {
+ .compatible = "samsung,exynos7-usbdrd-phy",
+ .data = &exynos7_usbdrd_phy
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, exynos5_usbdrd_phy_of_match);
+
+static int exynos5_usbdrd_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *node = dev->of_node;
+ struct exynos5_usbdrd_phy *phy_drd;
+ struct phy_provider *phy_provider;
+ struct resource *res;
+ const struct exynos5_usbdrd_phy_drvdata *drv_data;
+ struct regmap *reg_pmu;
+ u32 pmu_offset;
+ int i, ret;
+ int channel;
+
+ phy_drd = devm_kzalloc(dev, sizeof(*phy_drd), GFP_KERNEL);
+ if (!phy_drd)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, phy_drd);
+ phy_drd->dev = dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ phy_drd->reg_phy = devm_ioremap_resource(dev, res);
+ if (IS_ERR(phy_drd->reg_phy))
+ return PTR_ERR(phy_drd->reg_phy);
+
+ drv_data = of_device_get_match_data(dev);
+ if (!drv_data)
+ return -EINVAL;
+
+ phy_drd->drv_data = drv_data;
+
+ ret = exynos5_usbdrd_phy_clk_handle(phy_drd);
+ if (ret) {
+ dev_err(dev, "Failed to initialize clocks\n");
+ return ret;
+ }
+
+ reg_pmu = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "samsung,pmu-syscon");
+ if (IS_ERR(reg_pmu)) {
+ dev_err(dev, "Failed to lookup PMU regmap\n");
+ return PTR_ERR(reg_pmu);
+ }
+
+ /*
+ * Exynos5420 SoC has multiple channels for USB 3.0 PHY, with
+ * each having separate power control registers.
+ * 'channel' facilitates to set such registers.
+ */
+ channel = of_alias_get_id(node, "usbdrdphy");
+ if (channel < 0)
+ dev_dbg(dev, "Not a multi-controller usbdrd phy\n");
+
+ switch (channel) {
+ case 1:
+ pmu_offset = phy_drd->drv_data->pmu_offset_usbdrd1_phy;
+ break;
+ case 0:
+ default:
+ pmu_offset = phy_drd->drv_data->pmu_offset_usbdrd0_phy;
+ break;
+ }
+
+ /* Get Vbus regulators */
+ phy_drd->vbus = devm_regulator_get(dev, "vbus");
+ if (IS_ERR(phy_drd->vbus)) {
+ ret = PTR_ERR(phy_drd->vbus);
+ if (ret == -EPROBE_DEFER)
+ return ret;
+
+ dev_warn(dev, "Failed to get VBUS supply regulator\n");
+ phy_drd->vbus = NULL;
+ }
+
+ phy_drd->vbus_boost = devm_regulator_get(dev, "vbus-boost");
+ if (IS_ERR(phy_drd->vbus_boost)) {
+ ret = PTR_ERR(phy_drd->vbus_boost);
+ if (ret == -EPROBE_DEFER)
+ return ret;
+
+ dev_warn(dev, "Failed to get VBUS boost supply regulator\n");
+ phy_drd->vbus_boost = NULL;
+ }
+
+ dev_vdbg(dev, "Creating usbdrd_phy phy\n");
+
+ for (i = 0; i < EXYNOS5_DRDPHYS_NUM; i++) {
+ struct phy *phy = devm_phy_create(dev, NULL,
+ &exynos5_usbdrd_phy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "Failed to create usbdrd_phy phy\n");
+ return PTR_ERR(phy);
+ }
+
+ phy_drd->phys[i].phy = phy;
+ phy_drd->phys[i].index = i;
+ phy_drd->phys[i].reg_pmu = reg_pmu;
+ phy_drd->phys[i].pmu_offset = pmu_offset;
+ phy_drd->phys[i].phy_cfg = &drv_data->phy_cfg[i];
+ phy_set_drvdata(phy, &phy_drd->phys[i]);
+ }
+
+ phy_provider = devm_of_phy_provider_register(dev,
+ exynos5_usbdrd_phy_xlate);
+ if (IS_ERR(phy_provider)) {
+ dev_err(phy_drd->dev, "Failed to register phy provider\n");
+ return PTR_ERR(phy_provider);
+ }
+
+ return 0;
+}
+
+static struct platform_driver exynos5_usb3drd_phy = {
+ .probe = exynos5_usbdrd_phy_probe,
+ .driver = {
+ .of_match_table = exynos5_usbdrd_phy_of_match,
+ .name = "exynos5_usb3drd_phy",
+ }
+};
+
+module_platform_driver(exynos5_usb3drd_phy);
+MODULE_DESCRIPTION("Samsung EXYNOS5 SoCs USB 3.0 DRD controller PHY driver");
+MODULE_AUTHOR("Vivek Gautam <gautam.vivek@samsung.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:exynos5_usb3drd_phy");
diff --git a/drivers/phy/samsung/phy-exynos5250-sata.c b/drivers/phy/samsung/phy-exynos5250-sata.c
new file mode 100644
index 000000000..2c39d2fd3
--- /dev/null
+++ b/drivers/phy/samsung/phy-exynos5250-sata.c
@@ -0,0 +1,259 @@
+/*
+ * Samsung SATA SerDes(PHY) driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ * Authors: Girish K S <ks.giri@samsung.com>
+ * Yuvaraj Kumar C D <yuvaraj.cd@samsung.com>
+ *
+ * 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/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/spinlock.h>
+#include <linux/mfd/syscon.h>
+
+#define SATAPHY_CONTROL_OFFSET 0x0724
+#define EXYNOS5_SATAPHY_PMU_ENABLE BIT(0)
+#define EXYNOS5_SATA_RESET 0x4
+#define RESET_GLOBAL_RST_N BIT(0)
+#define RESET_CMN_RST_N BIT(1)
+#define RESET_CMN_BLOCK_RST_N BIT(2)
+#define RESET_CMN_I2C_RST_N BIT(3)
+#define RESET_TX_RX_PIPE_RST_N BIT(4)
+#define RESET_TX_RX_BLOCK_RST_N BIT(5)
+#define RESET_TX_RX_I2C_RST_N (BIT(6) | BIT(7))
+#define LINK_RESET 0xf0000
+#define EXYNOS5_SATA_MODE0 0x10
+#define SATA_SPD_GEN3 BIT(1)
+#define EXYNOS5_SATA_CTRL0 0x14
+#define CTRL0_P0_PHY_CALIBRATED_SEL BIT(9)
+#define CTRL0_P0_PHY_CALIBRATED BIT(8)
+#define EXYNOS5_SATA_PHSATA_CTRLM 0xe0
+#define PHCTRLM_REF_RATE BIT(1)
+#define PHCTRLM_HIGH_SPEED BIT(0)
+#define EXYNOS5_SATA_PHSATA_STATM 0xf0
+#define PHSTATM_PLL_LOCKED BIT(0)
+
+#define PHY_PLL_TIMEOUT (usecs_to_jiffies(1000))
+
+struct exynos_sata_phy {
+ struct phy *phy;
+ struct clk *phyclk;
+ void __iomem *regs;
+ struct regmap *pmureg;
+ struct i2c_client *client;
+};
+
+static int wait_for_reg_status(void __iomem *base, u32 reg, u32 checkbit,
+ u32 status)
+{
+ unsigned long timeout = jiffies + PHY_PLL_TIMEOUT;
+
+ while (time_before(jiffies, timeout)) {
+ if ((readl(base + reg) & checkbit) == status)
+ return 0;
+ }
+
+ return -EFAULT;
+}
+
+static int exynos_sata_phy_power_on(struct phy *phy)
+{
+ struct exynos_sata_phy *sata_phy = phy_get_drvdata(phy);
+
+ return regmap_update_bits(sata_phy->pmureg, SATAPHY_CONTROL_OFFSET,
+ EXYNOS5_SATAPHY_PMU_ENABLE, true);
+
+}
+
+static int exynos_sata_phy_power_off(struct phy *phy)
+{
+ struct exynos_sata_phy *sata_phy = phy_get_drvdata(phy);
+
+ return regmap_update_bits(sata_phy->pmureg, SATAPHY_CONTROL_OFFSET,
+ EXYNOS5_SATAPHY_PMU_ENABLE, false);
+
+}
+
+static int exynos_sata_phy_init(struct phy *phy)
+{
+ u32 val = 0;
+ int ret = 0;
+ u8 buf[] = { 0x3a, 0x0b };
+ struct exynos_sata_phy *sata_phy = phy_get_drvdata(phy);
+
+ ret = regmap_update_bits(sata_phy->pmureg, SATAPHY_CONTROL_OFFSET,
+ EXYNOS5_SATAPHY_PMU_ENABLE, true);
+ if (ret != 0)
+ dev_err(&sata_phy->phy->dev, "phy init failed\n");
+
+ writel(val, sata_phy->regs + EXYNOS5_SATA_RESET);
+
+ val = readl(sata_phy->regs + EXYNOS5_SATA_RESET);
+ val |= RESET_GLOBAL_RST_N | RESET_CMN_RST_N | RESET_CMN_BLOCK_RST_N
+ | RESET_CMN_I2C_RST_N | RESET_TX_RX_PIPE_RST_N
+ | RESET_TX_RX_BLOCK_RST_N | RESET_TX_RX_I2C_RST_N;
+ writel(val, sata_phy->regs + EXYNOS5_SATA_RESET);
+
+ val = readl(sata_phy->regs + EXYNOS5_SATA_RESET);
+ val |= LINK_RESET;
+ writel(val, sata_phy->regs + EXYNOS5_SATA_RESET);
+
+ val = readl(sata_phy->regs + EXYNOS5_SATA_RESET);
+ val |= RESET_CMN_RST_N;
+ writel(val, sata_phy->regs + EXYNOS5_SATA_RESET);
+
+ val = readl(sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM);
+ val &= ~PHCTRLM_REF_RATE;
+ writel(val, sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM);
+
+ /* High speed enable for Gen3 */
+ val = readl(sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM);
+ val |= PHCTRLM_HIGH_SPEED;
+ writel(val, sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM);
+
+ val = readl(sata_phy->regs + EXYNOS5_SATA_CTRL0);
+ val |= CTRL0_P0_PHY_CALIBRATED_SEL | CTRL0_P0_PHY_CALIBRATED;
+ writel(val, sata_phy->regs + EXYNOS5_SATA_CTRL0);
+
+ val = readl(sata_phy->regs + EXYNOS5_SATA_MODE0);
+ val |= SATA_SPD_GEN3;
+ writel(val, sata_phy->regs + EXYNOS5_SATA_MODE0);
+
+ ret = i2c_master_send(sata_phy->client, buf, sizeof(buf));
+ if (ret < 0)
+ return ret;
+
+ /* release cmu reset */
+ val = readl(sata_phy->regs + EXYNOS5_SATA_RESET);
+ val &= ~RESET_CMN_RST_N;
+ writel(val, sata_phy->regs + EXYNOS5_SATA_RESET);
+
+ val = readl(sata_phy->regs + EXYNOS5_SATA_RESET);
+ val |= RESET_CMN_RST_N;
+ writel(val, sata_phy->regs + EXYNOS5_SATA_RESET);
+
+ ret = wait_for_reg_status(sata_phy->regs,
+ EXYNOS5_SATA_PHSATA_STATM,
+ PHSTATM_PLL_LOCKED, 1);
+ if (ret < 0)
+ dev_err(&sata_phy->phy->dev,
+ "PHY PLL locking failed\n");
+ return ret;
+}
+
+static const struct phy_ops exynos_sata_phy_ops = {
+ .init = exynos_sata_phy_init,
+ .power_on = exynos_sata_phy_power_on,
+ .power_off = exynos_sata_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int exynos_sata_phy_probe(struct platform_device *pdev)
+{
+ struct exynos_sata_phy *sata_phy;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct phy_provider *phy_provider;
+ struct device_node *node;
+ int ret = 0;
+
+ sata_phy = devm_kzalloc(dev, sizeof(*sata_phy), GFP_KERNEL);
+ if (!sata_phy)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ sata_phy->regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(sata_phy->regs))
+ return PTR_ERR(sata_phy->regs);
+
+ sata_phy->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "samsung,syscon-phandle");
+ if (IS_ERR(sata_phy->pmureg)) {
+ dev_err(dev, "syscon regmap lookup failed.\n");
+ return PTR_ERR(sata_phy->pmureg);
+ }
+
+ node = of_parse_phandle(dev->of_node,
+ "samsung,exynos-sataphy-i2c-phandle", 0);
+ if (!node)
+ return -EINVAL;
+
+ sata_phy->client = of_find_i2c_device_by_node(node);
+ of_node_put(node);
+ if (!sata_phy->client)
+ return -EPROBE_DEFER;
+
+ dev_set_drvdata(dev, sata_phy);
+
+ sata_phy->phyclk = devm_clk_get(dev, "sata_phyctrl");
+ if (IS_ERR(sata_phy->phyclk)) {
+ dev_err(dev, "failed to get clk for PHY\n");
+ ret = PTR_ERR(sata_phy->phyclk);
+ goto put_dev;
+ }
+
+ ret = clk_prepare_enable(sata_phy->phyclk);
+ if (ret < 0) {
+ dev_err(dev, "failed to enable source clk\n");
+ goto put_dev;
+ }
+
+ sata_phy->phy = devm_phy_create(dev, NULL, &exynos_sata_phy_ops);
+ if (IS_ERR(sata_phy->phy)) {
+ dev_err(dev, "failed to create PHY\n");
+ ret = PTR_ERR(sata_phy->phy);
+ goto clk_disable;
+ }
+
+ phy_set_drvdata(sata_phy->phy, sata_phy);
+
+ phy_provider = devm_of_phy_provider_register(dev,
+ of_phy_simple_xlate);
+ if (IS_ERR(phy_provider)) {
+ ret = PTR_ERR(phy_provider);
+ goto clk_disable;
+ }
+
+ return 0;
+
+clk_disable:
+ clk_disable_unprepare(sata_phy->phyclk);
+put_dev:
+ put_device(&sata_phy->client->dev);
+
+ return ret;
+}
+
+static const struct of_device_id exynos_sata_phy_of_match[] = {
+ { .compatible = "samsung,exynos5250-sata-phy" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, exynos_sata_phy_of_match);
+
+static struct platform_driver exynos_sata_phy_driver = {
+ .probe = exynos_sata_phy_probe,
+ .driver = {
+ .of_match_table = exynos_sata_phy_of_match,
+ .name = "samsung,sata-phy",
+ }
+};
+module_platform_driver(exynos_sata_phy_driver);
+
+MODULE_DESCRIPTION("Samsung SerDes PHY driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Girish K S <ks.giri@samsung.com>");
+MODULE_AUTHOR("Yuvaraj C D <yuvaraj.cd@samsung.com>");
diff --git a/drivers/phy/samsung/phy-exynos5250-usb2.c b/drivers/phy/samsung/phy-exynos5250-usb2.c
new file mode 100644
index 000000000..aad806272
--- /dev/null
+++ b/drivers/phy/samsung/phy-exynos5250-usb2.c
@@ -0,0 +1,401 @@
+/*
+ * Samsung SoC USB 1.1/2.0 PHY driver - Exynos 5250 support
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ * Author: Kamil Debski <k.debski@samsung.com>
+ *
+ * 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/delay.h>
+#include <linux/io.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+#include "phy-samsung-usb2.h"
+
+/* Exynos USB PHY registers */
+#define EXYNOS_5250_REFCLKSEL_CRYSTAL 0x0
+#define EXYNOS_5250_REFCLKSEL_XO 0x1
+#define EXYNOS_5250_REFCLKSEL_CLKCORE 0x2
+
+#define EXYNOS_5250_FSEL_9MHZ6 0x0
+#define EXYNOS_5250_FSEL_10MHZ 0x1
+#define EXYNOS_5250_FSEL_12MHZ 0x2
+#define EXYNOS_5250_FSEL_19MHZ2 0x3
+#define EXYNOS_5250_FSEL_20MHZ 0x4
+#define EXYNOS_5250_FSEL_24MHZ 0x5
+#define EXYNOS_5250_FSEL_50MHZ 0x7
+
+/* Normal host */
+#define EXYNOS_5250_HOSTPHYCTRL0 0x0
+
+#define EXYNOS_5250_HOSTPHYCTRL0_PHYSWRSTALL BIT(31)
+#define EXYNOS_5250_HOSTPHYCTRL0_REFCLKSEL_SHIFT 19
+#define EXYNOS_5250_HOSTPHYCTRL0_REFCLKSEL_MASK \
+ (0x3 << EXYNOS_5250_HOSTPHYCTRL0_REFCLKSEL_SHIFT)
+#define EXYNOS_5250_HOSTPHYCTRL0_FSEL_SHIFT 16
+#define EXYNOS_5250_HOSTPHYCTRL0_FSEL_MASK \
+ (0x7 << EXYNOS_5250_HOSTPHYCTRL0_FSEL_SHIFT)
+#define EXYNOS_5250_HOSTPHYCTRL0_TESTBURNIN BIT(11)
+#define EXYNOS_5250_HOSTPHYCTRL0_RETENABLE BIT(10)
+#define EXYNOS_5250_HOSTPHYCTRL0_COMMON_ON_N BIT(9)
+#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_MASK (0x3 << 7)
+#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_DUAL (0x0 << 7)
+#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_ID0 (0x1 << 7)
+#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_ANALOGTEST (0x2 << 7)
+#define EXYNOS_5250_HOSTPHYCTRL0_SIDDQ BIT(6)
+#define EXYNOS_5250_HOSTPHYCTRL0_FORCESLEEP BIT(5)
+#define EXYNOS_5250_HOSTPHYCTRL0_FORCESUSPEND BIT(4)
+#define EXYNOS_5250_HOSTPHYCTRL0_WORDINTERFACE BIT(3)
+#define EXYNOS_5250_HOSTPHYCTRL0_UTMISWRST BIT(2)
+#define EXYNOS_5250_HOSTPHYCTRL0_LINKSWRST BIT(1)
+#define EXYNOS_5250_HOSTPHYCTRL0_PHYSWRST BIT(0)
+
+/* HSIC0 & HSIC1 */
+#define EXYNOS_5250_HSICPHYCTRL1 0x10
+#define EXYNOS_5250_HSICPHYCTRL2 0x20
+
+#define EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_MASK (0x3 << 23)
+#define EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_DEFAULT (0x2 << 23)
+#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_MASK (0x7f << 16)
+#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_12 (0x24 << 16)
+#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_15 (0x1c << 16)
+#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_16 (0x1a << 16)
+#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_19_2 (0x15 << 16)
+#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_20 (0x14 << 16)
+#define EXYNOS_5250_HSICPHYCTRLX_SIDDQ BIT(6)
+#define EXYNOS_5250_HSICPHYCTRLX_FORCESLEEP BIT(5)
+#define EXYNOS_5250_HSICPHYCTRLX_FORCESUSPEND BIT(4)
+#define EXYNOS_5250_HSICPHYCTRLX_WORDINTERFACE BIT(3)
+#define EXYNOS_5250_HSICPHYCTRLX_UTMISWRST BIT(2)
+#define EXYNOS_5250_HSICPHYCTRLX_PHYSWRST BIT(0)
+
+/* EHCI control */
+#define EXYNOS_5250_HOSTEHCICTRL 0x30
+#define EXYNOS_5250_HOSTEHCICTRL_ENAINCRXALIGN BIT(29)
+#define EXYNOS_5250_HOSTEHCICTRL_ENAINCR4 BIT(28)
+#define EXYNOS_5250_HOSTEHCICTRL_ENAINCR8 BIT(27)
+#define EXYNOS_5250_HOSTEHCICTRL_ENAINCR16 BIT(26)
+#define EXYNOS_5250_HOSTEHCICTRL_AUTOPPDONOVRCUREN BIT(25)
+#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_SHIFT 19
+#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_MASK \
+ (0x3f << EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_SHIFT)
+#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL1_SHIFT 13
+#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL1_MASK \
+ (0x3f << EXYNOS_5250_HOSTEHCICTRL_FLADJVAL1_SHIFT)
+#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL2_SHIFT 7
+#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_MASK \
+ (0x3f << EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_SHIFT)
+#define EXYNOS_5250_HOSTEHCICTRL_FLADJVALHOST_SHIFT 1
+#define EXYNOS_5250_HOSTEHCICTRL_FLADJVALHOST_MASK \
+ (0x1 << EXYNOS_5250_HOSTEHCICTRL_FLADJVALHOST_SHIFT)
+#define EXYNOS_5250_HOSTEHCICTRL_SIMULATIONMODE BIT(0)
+
+/* OHCI control */
+#define EXYNOS_5250_HOSTOHCICTRL 0x34
+#define EXYNOS_5250_HOSTOHCICTRL_FRAMELENVAL_SHIFT 1
+#define EXYNOS_5250_HOSTOHCICTRL_FRAMELENVAL_MASK \
+ (0x3ff << EXYNOS_5250_HOSTOHCICTRL_FRAMELENVAL_SHIFT)
+#define EXYNOS_5250_HOSTOHCICTRL_FRAMELENVALEN BIT(0)
+
+/* USBOTG */
+#define EXYNOS_5250_USBOTGSYS 0x38
+#define EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET BIT(14)
+#define EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG BIT(13)
+#define EXYNOS_5250_USBOTGSYS_PHY_SW_RST BIT(12)
+#define EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT 9
+#define EXYNOS_5250_USBOTGSYS_REFCLKSEL_MASK \
+ (0x3 << EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT)
+#define EXYNOS_5250_USBOTGSYS_ID_PULLUP BIT(8)
+#define EXYNOS_5250_USBOTGSYS_COMMON_ON BIT(7)
+#define EXYNOS_5250_USBOTGSYS_FSEL_SHIFT 4
+#define EXYNOS_5250_USBOTGSYS_FSEL_MASK \
+ (0x3 << EXYNOS_5250_USBOTGSYS_FSEL_SHIFT)
+#define EXYNOS_5250_USBOTGSYS_FORCE_SLEEP BIT(3)
+#define EXYNOS_5250_USBOTGSYS_OTGDISABLE BIT(2)
+#define EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG BIT(1)
+#define EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND BIT(0)
+
+/* Isolation, configured in the power management unit */
+#define EXYNOS_5250_USB_ISOL_OTG_OFFSET 0x704
+#define EXYNOS_5250_USB_ISOL_OTG BIT(0)
+#define EXYNOS_5250_USB_ISOL_HOST_OFFSET 0x708
+#define EXYNOS_5250_USB_ISOL_HOST BIT(0)
+
+/* Mode swtich register */
+#define EXYNOS_5250_MODE_SWITCH_OFFSET 0x230
+#define EXYNOS_5250_MODE_SWITCH_MASK 1
+#define EXYNOS_5250_MODE_SWITCH_DEVICE 0
+#define EXYNOS_5250_MODE_SWITCH_HOST 1
+
+enum exynos4x12_phy_id {
+ EXYNOS5250_DEVICE,
+ EXYNOS5250_HOST,
+ EXYNOS5250_HSIC0,
+ EXYNOS5250_HSIC1,
+ EXYNOS5250_NUM_PHYS,
+};
+
+/*
+ * exynos5250_rate_to_clk() converts the supplied clock rate to the value that
+ * can be written to the phy register.
+ */
+static int exynos5250_rate_to_clk(unsigned long rate, u32 *reg)
+{
+ /* EXYNOS_5250_FSEL_MASK */
+
+ switch (rate) {
+ case 9600 * KHZ:
+ *reg = EXYNOS_5250_FSEL_9MHZ6;
+ break;
+ case 10 * MHZ:
+ *reg = EXYNOS_5250_FSEL_10MHZ;
+ break;
+ case 12 * MHZ:
+ *reg = EXYNOS_5250_FSEL_12MHZ;
+ break;
+ case 19200 * KHZ:
+ *reg = EXYNOS_5250_FSEL_19MHZ2;
+ break;
+ case 20 * MHZ:
+ *reg = EXYNOS_5250_FSEL_20MHZ;
+ break;
+ case 24 * MHZ:
+ *reg = EXYNOS_5250_FSEL_24MHZ;
+ break;
+ case 50 * MHZ:
+ *reg = EXYNOS_5250_FSEL_50MHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void exynos5250_isol(struct samsung_usb2_phy_instance *inst, bool on)
+{
+ struct samsung_usb2_phy_driver *drv = inst->drv;
+ u32 offset;
+ u32 mask;
+
+ switch (inst->cfg->id) {
+ case EXYNOS5250_DEVICE:
+ offset = EXYNOS_5250_USB_ISOL_OTG_OFFSET;
+ mask = EXYNOS_5250_USB_ISOL_OTG;
+ break;
+ case EXYNOS5250_HOST:
+ offset = EXYNOS_5250_USB_ISOL_HOST_OFFSET;
+ mask = EXYNOS_5250_USB_ISOL_HOST;
+ break;
+ default:
+ return;
+ }
+
+ regmap_update_bits(drv->reg_pmu, offset, mask, on ? 0 : mask);
+}
+
+static int exynos5250_power_on(struct samsung_usb2_phy_instance *inst)
+{
+ struct samsung_usb2_phy_driver *drv = inst->drv;
+ u32 ctrl0;
+ u32 otg;
+ u32 ehci;
+ u32 ohci;
+ u32 hsic;
+
+ switch (inst->cfg->id) {
+ case EXYNOS5250_DEVICE:
+ regmap_update_bits(drv->reg_sys,
+ EXYNOS_5250_MODE_SWITCH_OFFSET,
+ EXYNOS_5250_MODE_SWITCH_MASK,
+ EXYNOS_5250_MODE_SWITCH_DEVICE);
+
+ /* OTG configuration */
+ otg = readl(drv->reg_phy + EXYNOS_5250_USBOTGSYS);
+ /* The clock */
+ otg &= ~EXYNOS_5250_USBOTGSYS_FSEL_MASK;
+ otg |= drv->ref_reg_val << EXYNOS_5250_USBOTGSYS_FSEL_SHIFT;
+ /* Reset */
+ otg &= ~(EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND |
+ EXYNOS_5250_USBOTGSYS_FORCE_SLEEP |
+ EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG);
+ otg |= EXYNOS_5250_USBOTGSYS_PHY_SW_RST |
+ EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET |
+ EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG |
+ EXYNOS_5250_USBOTGSYS_OTGDISABLE;
+ /* Ref clock */
+ otg &= ~EXYNOS_5250_USBOTGSYS_REFCLKSEL_MASK;
+ otg |= EXYNOS_5250_REFCLKSEL_CLKCORE <<
+ EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT;
+ writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS);
+ udelay(100);
+ otg &= ~(EXYNOS_5250_USBOTGSYS_PHY_SW_RST |
+ EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG |
+ EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET |
+ EXYNOS_5250_USBOTGSYS_OTGDISABLE);
+ writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS);
+
+
+ break;
+ case EXYNOS5250_HOST:
+ case EXYNOS5250_HSIC0:
+ case EXYNOS5250_HSIC1:
+ /* Host registers configuration */
+ ctrl0 = readl(drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0);
+ /* The clock */
+ ctrl0 &= ~EXYNOS_5250_HOSTPHYCTRL0_FSEL_MASK;
+ ctrl0 |= drv->ref_reg_val <<
+ EXYNOS_5250_HOSTPHYCTRL0_FSEL_SHIFT;
+
+ /* Reset */
+ ctrl0 &= ~(EXYNOS_5250_HOSTPHYCTRL0_PHYSWRST |
+ EXYNOS_5250_HOSTPHYCTRL0_PHYSWRSTALL |
+ EXYNOS_5250_HOSTPHYCTRL0_SIDDQ |
+ EXYNOS_5250_HOSTPHYCTRL0_FORCESUSPEND |
+ EXYNOS_5250_HOSTPHYCTRL0_FORCESLEEP);
+ ctrl0 |= EXYNOS_5250_HOSTPHYCTRL0_LINKSWRST |
+ EXYNOS_5250_HOSTPHYCTRL0_UTMISWRST |
+ EXYNOS_5250_HOSTPHYCTRL0_COMMON_ON_N;
+ writel(ctrl0, drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0);
+ udelay(10);
+ ctrl0 &= ~(EXYNOS_5250_HOSTPHYCTRL0_LINKSWRST |
+ EXYNOS_5250_HOSTPHYCTRL0_UTMISWRST);
+ writel(ctrl0, drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0);
+
+ /* OTG configuration */
+ otg = readl(drv->reg_phy + EXYNOS_5250_USBOTGSYS);
+ /* The clock */
+ otg &= ~EXYNOS_5250_USBOTGSYS_FSEL_MASK;
+ otg |= drv->ref_reg_val << EXYNOS_5250_USBOTGSYS_FSEL_SHIFT;
+ /* Reset */
+ otg &= ~(EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND |
+ EXYNOS_5250_USBOTGSYS_FORCE_SLEEP |
+ EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG);
+ otg |= EXYNOS_5250_USBOTGSYS_PHY_SW_RST |
+ EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET |
+ EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG |
+ EXYNOS_5250_USBOTGSYS_OTGDISABLE;
+ /* Ref clock */
+ otg &= ~EXYNOS_5250_USBOTGSYS_REFCLKSEL_MASK;
+ otg |= EXYNOS_5250_REFCLKSEL_CLKCORE <<
+ EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT;
+ writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS);
+ udelay(10);
+ otg &= ~(EXYNOS_5250_USBOTGSYS_PHY_SW_RST |
+ EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG |
+ EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET);
+
+ /* HSIC phy configuration */
+ hsic = (EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_12 |
+ EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_DEFAULT |
+ EXYNOS_5250_HSICPHYCTRLX_PHYSWRST);
+ writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL1);
+ writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL2);
+ udelay(10);
+ hsic &= ~EXYNOS_5250_HSICPHYCTRLX_PHYSWRST;
+ writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL1);
+ writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL2);
+ /* The following delay is necessary for the reset sequence to be
+ * completed */
+ udelay(80);
+
+ /* Enable EHCI DMA burst */
+ ehci = readl(drv->reg_phy + EXYNOS_5250_HOSTEHCICTRL);
+ ehci |= EXYNOS_5250_HOSTEHCICTRL_ENAINCRXALIGN |
+ EXYNOS_5250_HOSTEHCICTRL_ENAINCR4 |
+ EXYNOS_5250_HOSTEHCICTRL_ENAINCR8 |
+ EXYNOS_5250_HOSTEHCICTRL_ENAINCR16;
+ writel(ehci, drv->reg_phy + EXYNOS_5250_HOSTEHCICTRL);
+
+ /* OHCI settings */
+ ohci = readl(drv->reg_phy + EXYNOS_5250_HOSTOHCICTRL);
+ /* Following code is based on the old driver */
+ ohci |= 0x1 << 3;
+ writel(ohci, drv->reg_phy + EXYNOS_5250_HOSTOHCICTRL);
+
+ break;
+ }
+ exynos5250_isol(inst, 0);
+
+ return 0;
+}
+
+static int exynos5250_power_off(struct samsung_usb2_phy_instance *inst)
+{
+ struct samsung_usb2_phy_driver *drv = inst->drv;
+ u32 ctrl0;
+ u32 otg;
+ u32 hsic;
+
+ exynos5250_isol(inst, 1);
+
+ switch (inst->cfg->id) {
+ case EXYNOS5250_DEVICE:
+ otg = readl(drv->reg_phy + EXYNOS_5250_USBOTGSYS);
+ otg |= (EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND |
+ EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG |
+ EXYNOS_5250_USBOTGSYS_FORCE_SLEEP);
+ writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS);
+ break;
+ case EXYNOS5250_HOST:
+ ctrl0 = readl(drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0);
+ ctrl0 |= (EXYNOS_5250_HOSTPHYCTRL0_SIDDQ |
+ EXYNOS_5250_HOSTPHYCTRL0_FORCESUSPEND |
+ EXYNOS_5250_HOSTPHYCTRL0_FORCESLEEP |
+ EXYNOS_5250_HOSTPHYCTRL0_PHYSWRST |
+ EXYNOS_5250_HOSTPHYCTRL0_PHYSWRSTALL);
+ writel(ctrl0, drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0);
+ break;
+ case EXYNOS5250_HSIC0:
+ case EXYNOS5250_HSIC1:
+ hsic = (EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_12 |
+ EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_DEFAULT |
+ EXYNOS_5250_HSICPHYCTRLX_SIDDQ |
+ EXYNOS_5250_HSICPHYCTRLX_FORCESLEEP |
+ EXYNOS_5250_HSICPHYCTRLX_FORCESUSPEND
+ );
+ writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL1);
+ writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL2);
+ break;
+ }
+
+ return 0;
+}
+
+
+static const struct samsung_usb2_common_phy exynos5250_phys[] = {
+ {
+ .label = "device",
+ .id = EXYNOS5250_DEVICE,
+ .power_on = exynos5250_power_on,
+ .power_off = exynos5250_power_off,
+ },
+ {
+ .label = "host",
+ .id = EXYNOS5250_HOST,
+ .power_on = exynos5250_power_on,
+ .power_off = exynos5250_power_off,
+ },
+ {
+ .label = "hsic0",
+ .id = EXYNOS5250_HSIC0,
+ .power_on = exynos5250_power_on,
+ .power_off = exynos5250_power_off,
+ },
+ {
+ .label = "hsic1",
+ .id = EXYNOS5250_HSIC1,
+ .power_on = exynos5250_power_on,
+ .power_off = exynos5250_power_off,
+ },
+};
+
+const struct samsung_usb2_phy_config exynos5250_usb2_phy_config = {
+ .has_mode_switch = 1,
+ .num_phys = EXYNOS5250_NUM_PHYS,
+ .phys = exynos5250_phys,
+ .rate_to_clk = exynos5250_rate_to_clk,
+};
diff --git a/drivers/phy/samsung/phy-s5pv210-usb2.c b/drivers/phy/samsung/phy-s5pv210-usb2.c
new file mode 100644
index 000000000..bb7fdf491
--- /dev/null
+++ b/drivers/phy/samsung/phy-s5pv210-usb2.c
@@ -0,0 +1,191 @@
+/*
+ * Samsung SoC USB 1.1/2.0 PHY driver - S5PV210 support
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ * Authors: Kamil Debski <k.debski@samsung.com>
+ *
+ * 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/delay.h>
+#include <linux/io.h>
+#include <linux/phy/phy.h>
+#include "phy-samsung-usb2.h"
+
+/* Exynos USB PHY registers */
+
+/* PHY power control */
+#define S5PV210_UPHYPWR 0x0
+
+#define S5PV210_UPHYPWR_PHY0_SUSPEND BIT(0)
+#define S5PV210_UPHYPWR_PHY0_PWR BIT(3)
+#define S5PV210_UPHYPWR_PHY0_OTG_PWR BIT(4)
+#define S5PV210_UPHYPWR_PHY0 ( \
+ S5PV210_UPHYPWR_PHY0_SUSPEND | \
+ S5PV210_UPHYPWR_PHY0_PWR | \
+ S5PV210_UPHYPWR_PHY0_OTG_PWR)
+
+#define S5PV210_UPHYPWR_PHY1_SUSPEND BIT(6)
+#define S5PV210_UPHYPWR_PHY1_PWR BIT(7)
+#define S5PV210_UPHYPWR_PHY1 ( \
+ S5PV210_UPHYPWR_PHY1_SUSPEND | \
+ S5PV210_UPHYPWR_PHY1_PWR)
+
+/* PHY clock control */
+#define S5PV210_UPHYCLK 0x4
+
+#define S5PV210_UPHYCLK_PHYFSEL_MASK (0x3 << 0)
+#define S5PV210_UPHYCLK_PHYFSEL_48MHZ (0x0 << 0)
+#define S5PV210_UPHYCLK_PHYFSEL_24MHZ (0x3 << 0)
+#define S5PV210_UPHYCLK_PHYFSEL_12MHZ (0x2 << 0)
+
+#define S5PV210_UPHYCLK_PHY0_ID_PULLUP BIT(2)
+#define S5PV210_UPHYCLK_PHY0_COMMON_ON BIT(4)
+#define S5PV210_UPHYCLK_PHY1_COMMON_ON BIT(7)
+
+/* PHY reset control */
+#define S5PV210_UPHYRST 0x8
+
+#define S5PV210_URSTCON_PHY0 BIT(0)
+#define S5PV210_URSTCON_OTG_HLINK BIT(1)
+#define S5PV210_URSTCON_OTG_PHYLINK BIT(2)
+#define S5PV210_URSTCON_PHY1_ALL BIT(3)
+#define S5PV210_URSTCON_HOST_LINK_ALL BIT(4)
+
+/* Isolation, configured in the power management unit */
+#define S5PV210_USB_ISOL_OFFSET 0x680c
+#define S5PV210_USB_ISOL_DEVICE BIT(0)
+#define S5PV210_USB_ISOL_HOST BIT(1)
+
+
+enum s5pv210_phy_id {
+ S5PV210_DEVICE,
+ S5PV210_HOST,
+ S5PV210_NUM_PHYS,
+};
+
+/*
+ * s5pv210_rate_to_clk() converts the supplied clock rate to the value that
+ * can be written to the phy register.
+ */
+static int s5pv210_rate_to_clk(unsigned long rate, u32 *reg)
+{
+ switch (rate) {
+ case 12 * MHZ:
+ *reg = S5PV210_UPHYCLK_PHYFSEL_12MHZ;
+ break;
+ case 24 * MHZ:
+ *reg = S5PV210_UPHYCLK_PHYFSEL_24MHZ;
+ break;
+ case 48 * MHZ:
+ *reg = S5PV210_UPHYCLK_PHYFSEL_48MHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void s5pv210_isol(struct samsung_usb2_phy_instance *inst, bool on)
+{
+ struct samsung_usb2_phy_driver *drv = inst->drv;
+ u32 mask;
+
+ switch (inst->cfg->id) {
+ case S5PV210_DEVICE:
+ mask = S5PV210_USB_ISOL_DEVICE;
+ break;
+ case S5PV210_HOST:
+ mask = S5PV210_USB_ISOL_HOST;
+ break;
+ default:
+ return;
+ }
+
+ regmap_update_bits(drv->reg_pmu, S5PV210_USB_ISOL_OFFSET,
+ mask, on ? 0 : mask);
+}
+
+static void s5pv210_phy_pwr(struct samsung_usb2_phy_instance *inst, bool on)
+{
+ struct samsung_usb2_phy_driver *drv = inst->drv;
+ u32 rstbits = 0;
+ u32 phypwr = 0;
+ u32 rst;
+ u32 pwr;
+
+ switch (inst->cfg->id) {
+ case S5PV210_DEVICE:
+ phypwr = S5PV210_UPHYPWR_PHY0;
+ rstbits = S5PV210_URSTCON_PHY0;
+ break;
+ case S5PV210_HOST:
+ phypwr = S5PV210_UPHYPWR_PHY1;
+ rstbits = S5PV210_URSTCON_PHY1_ALL |
+ S5PV210_URSTCON_HOST_LINK_ALL;
+ break;
+ }
+
+ if (on) {
+ writel(drv->ref_reg_val, drv->reg_phy + S5PV210_UPHYCLK);
+
+ pwr = readl(drv->reg_phy + S5PV210_UPHYPWR);
+ pwr &= ~phypwr;
+ writel(pwr, drv->reg_phy + S5PV210_UPHYPWR);
+
+ rst = readl(drv->reg_phy + S5PV210_UPHYRST);
+ rst |= rstbits;
+ writel(rst, drv->reg_phy + S5PV210_UPHYRST);
+ udelay(10);
+ rst &= ~rstbits;
+ writel(rst, drv->reg_phy + S5PV210_UPHYRST);
+ /* The following delay is necessary for the reset sequence to be
+ * completed
+ */
+ udelay(80);
+ } else {
+ pwr = readl(drv->reg_phy + S5PV210_UPHYPWR);
+ pwr |= phypwr;
+ writel(pwr, drv->reg_phy + S5PV210_UPHYPWR);
+ }
+}
+
+static int s5pv210_power_on(struct samsung_usb2_phy_instance *inst)
+{
+ s5pv210_isol(inst, 0);
+ s5pv210_phy_pwr(inst, 1);
+
+ return 0;
+}
+
+static int s5pv210_power_off(struct samsung_usb2_phy_instance *inst)
+{
+ s5pv210_phy_pwr(inst, 0);
+ s5pv210_isol(inst, 1);
+
+ return 0;
+}
+
+static const struct samsung_usb2_common_phy s5pv210_phys[S5PV210_NUM_PHYS] = {
+ [S5PV210_DEVICE] = {
+ .label = "device",
+ .id = S5PV210_DEVICE,
+ .power_on = s5pv210_power_on,
+ .power_off = s5pv210_power_off,
+ },
+ [S5PV210_HOST] = {
+ .label = "host",
+ .id = S5PV210_HOST,
+ .power_on = s5pv210_power_on,
+ .power_off = s5pv210_power_off,
+ },
+};
+
+const struct samsung_usb2_phy_config s5pv210_usb2_phy_config = {
+ .num_phys = ARRAY_SIZE(s5pv210_phys),
+ .phys = s5pv210_phys,
+ .rate_to_clk = s5pv210_rate_to_clk,
+};
diff --git a/drivers/phy/samsung/phy-samsung-usb2.c b/drivers/phy/samsung/phy-samsung-usb2.c
new file mode 100644
index 000000000..ea8188669
--- /dev/null
+++ b/drivers/phy/samsung/phy-samsung-usb2.c
@@ -0,0 +1,264 @@
+/*
+ * Samsung SoC USB 1.1/2.0 PHY driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ * Author: Kamil Debski <k.debski@samsung.com>
+ *
+ * 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/clk.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include "phy-samsung-usb2.h"
+
+static int samsung_usb2_phy_power_on(struct phy *phy)
+{
+ struct samsung_usb2_phy_instance *inst = phy_get_drvdata(phy);
+ struct samsung_usb2_phy_driver *drv = inst->drv;
+ int ret;
+
+ dev_dbg(drv->dev, "Request to power_on \"%s\" usb phy\n",
+ inst->cfg->label);
+
+ if (drv->vbus) {
+ ret = regulator_enable(drv->vbus);
+ if (ret)
+ goto err_regulator;
+ }
+
+ ret = clk_prepare_enable(drv->clk);
+ if (ret)
+ goto err_main_clk;
+ ret = clk_prepare_enable(drv->ref_clk);
+ if (ret)
+ goto err_instance_clk;
+ if (inst->cfg->power_on) {
+ spin_lock(&drv->lock);
+ ret = inst->cfg->power_on(inst);
+ spin_unlock(&drv->lock);
+ if (ret)
+ goto err_power_on;
+ }
+
+ return 0;
+
+err_power_on:
+ clk_disable_unprepare(drv->ref_clk);
+err_instance_clk:
+ clk_disable_unprepare(drv->clk);
+err_main_clk:
+ if (drv->vbus)
+ regulator_disable(drv->vbus);
+err_regulator:
+ return ret;
+}
+
+static int samsung_usb2_phy_power_off(struct phy *phy)
+{
+ struct samsung_usb2_phy_instance *inst = phy_get_drvdata(phy);
+ struct samsung_usb2_phy_driver *drv = inst->drv;
+ int ret = 0;
+
+ dev_dbg(drv->dev, "Request to power_off \"%s\" usb phy\n",
+ inst->cfg->label);
+ if (inst->cfg->power_off) {
+ spin_lock(&drv->lock);
+ ret = inst->cfg->power_off(inst);
+ spin_unlock(&drv->lock);
+ if (ret)
+ return ret;
+ }
+ clk_disable_unprepare(drv->ref_clk);
+ clk_disable_unprepare(drv->clk);
+ if (drv->vbus)
+ ret = regulator_disable(drv->vbus);
+
+ return ret;
+}
+
+static const struct phy_ops samsung_usb2_phy_ops = {
+ .power_on = samsung_usb2_phy_power_on,
+ .power_off = samsung_usb2_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static struct phy *samsung_usb2_phy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct samsung_usb2_phy_driver *drv;
+
+ drv = dev_get_drvdata(dev);
+ if (!drv)
+ return ERR_PTR(-EINVAL);
+
+ if (WARN_ON(args->args[0] >= drv->cfg->num_phys))
+ return ERR_PTR(-ENODEV);
+
+ return drv->instances[args->args[0]].phy;
+}
+
+static const struct of_device_id samsung_usb2_phy_of_match[] = {
+#ifdef CONFIG_PHY_EXYNOS4X12_USB2
+ {
+ .compatible = "samsung,exynos3250-usb2-phy",
+ .data = &exynos3250_usb2_phy_config,
+ },
+#endif
+#ifdef CONFIG_PHY_EXYNOS4210_USB2
+ {
+ .compatible = "samsung,exynos4210-usb2-phy",
+ .data = &exynos4210_usb2_phy_config,
+ },
+#endif
+#ifdef CONFIG_PHY_EXYNOS4X12_USB2
+ {
+ .compatible = "samsung,exynos4x12-usb2-phy",
+ .data = &exynos4x12_usb2_phy_config,
+ },
+#endif
+#ifdef CONFIG_PHY_EXYNOS5250_USB2
+ {
+ .compatible = "samsung,exynos5250-usb2-phy",
+ .data = &exynos5250_usb2_phy_config,
+ },
+#endif
+#ifdef CONFIG_PHY_S5PV210_USB2
+ {
+ .compatible = "samsung,s5pv210-usb2-phy",
+ .data = &s5pv210_usb2_phy_config,
+ },
+#endif
+ { },
+};
+MODULE_DEVICE_TABLE(of, samsung_usb2_phy_of_match);
+
+static int samsung_usb2_phy_probe(struct platform_device *pdev)
+{
+ const struct samsung_usb2_phy_config *cfg;
+ struct device *dev = &pdev->dev;
+ struct phy_provider *phy_provider;
+ struct resource *mem;
+ struct samsung_usb2_phy_driver *drv;
+ int i, ret;
+
+ if (!pdev->dev.of_node) {
+ dev_err(dev, "This driver is required to be instantiated from device tree\n");
+ return -EINVAL;
+ }
+
+ cfg = of_device_get_match_data(dev);
+ if (!cfg)
+ return -EINVAL;
+
+ drv = devm_kzalloc(dev, sizeof(struct samsung_usb2_phy_driver) +
+ cfg->num_phys * sizeof(struct samsung_usb2_phy_instance),
+ GFP_KERNEL);
+ if (!drv)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, drv);
+ spin_lock_init(&drv->lock);
+
+ drv->cfg = cfg;
+ drv->dev = dev;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ drv->reg_phy = devm_ioremap_resource(dev, mem);
+ if (IS_ERR(drv->reg_phy)) {
+ dev_err(dev, "Failed to map register memory (phy)\n");
+ return PTR_ERR(drv->reg_phy);
+ }
+
+ drv->reg_pmu = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+ "samsung,pmureg-phandle");
+ if (IS_ERR(drv->reg_pmu)) {
+ dev_err(dev, "Failed to map PMU registers (via syscon)\n");
+ return PTR_ERR(drv->reg_pmu);
+ }
+
+ if (drv->cfg->has_mode_switch) {
+ drv->reg_sys = syscon_regmap_lookup_by_phandle(
+ pdev->dev.of_node, "samsung,sysreg-phandle");
+ if (IS_ERR(drv->reg_sys)) {
+ dev_err(dev, "Failed to map system registers (via syscon)\n");
+ return PTR_ERR(drv->reg_sys);
+ }
+ }
+
+ drv->clk = devm_clk_get(dev, "phy");
+ if (IS_ERR(drv->clk)) {
+ dev_err(dev, "Failed to get clock of phy controller\n");
+ return PTR_ERR(drv->clk);
+ }
+
+ drv->ref_clk = devm_clk_get(dev, "ref");
+ if (IS_ERR(drv->ref_clk)) {
+ dev_err(dev, "Failed to get reference clock for the phy controller\n");
+ return PTR_ERR(drv->ref_clk);
+ }
+
+ drv->ref_rate = clk_get_rate(drv->ref_clk);
+ if (drv->cfg->rate_to_clk) {
+ ret = drv->cfg->rate_to_clk(drv->ref_rate, &drv->ref_reg_val);
+ if (ret)
+ return ret;
+ }
+
+ drv->vbus = devm_regulator_get(dev, "vbus");
+ if (IS_ERR(drv->vbus)) {
+ ret = PTR_ERR(drv->vbus);
+ if (ret == -EPROBE_DEFER)
+ return ret;
+ drv->vbus = NULL;
+ }
+
+ for (i = 0; i < drv->cfg->num_phys; i++) {
+ char *label = drv->cfg->phys[i].label;
+ struct samsung_usb2_phy_instance *p = &drv->instances[i];
+
+ dev_dbg(dev, "Creating phy \"%s\"\n", label);
+ p->phy = devm_phy_create(dev, NULL, &samsung_usb2_phy_ops);
+ if (IS_ERR(p->phy)) {
+ dev_err(drv->dev, "Failed to create usb2_phy \"%s\"\n",
+ label);
+ return PTR_ERR(p->phy);
+ }
+
+ p->cfg = &drv->cfg->phys[i];
+ p->drv = drv;
+ phy_set_bus_width(p->phy, 8);
+ phy_set_drvdata(p->phy, p);
+ }
+
+ phy_provider = devm_of_phy_provider_register(dev,
+ samsung_usb2_phy_xlate);
+ if (IS_ERR(phy_provider)) {
+ dev_err(drv->dev, "Failed to register phy provider\n");
+ return PTR_ERR(phy_provider);
+ }
+
+ return 0;
+}
+
+static struct platform_driver samsung_usb2_phy_driver = {
+ .probe = samsung_usb2_phy_probe,
+ .driver = {
+ .of_match_table = samsung_usb2_phy_of_match,
+ .name = "samsung-usb2-phy",
+ }
+};
+
+module_platform_driver(samsung_usb2_phy_driver);
+MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC USB PHY driver");
+MODULE_AUTHOR("Kamil Debski <k.debski@samsung.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:samsung-usb2-phy");
diff --git a/drivers/phy/samsung/phy-samsung-usb2.h b/drivers/phy/samsung/phy-samsung-usb2.h
new file mode 100644
index 000000000..6563e7ca0
--- /dev/null
+++ b/drivers/phy/samsung/phy-samsung-usb2.h
@@ -0,0 +1,73 @@
+/*
+ * Samsung SoC USB 1.1/2.0 PHY driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ * Author: Kamil Debski <k.debski@samsung.com>
+ *
+ * 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.
+ */
+
+#ifndef _PHY_EXYNOS_USB2_H
+#define _PHY_EXYNOS_USB2_H
+
+#include <linux/clk.h>
+#include <linux/phy/phy.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <linux/spinlock.h>
+#include <linux/regulator/consumer.h>
+
+#define KHZ 1000
+#define MHZ (KHZ * KHZ)
+
+struct samsung_usb2_phy_driver;
+struct samsung_usb2_phy_instance;
+struct samsung_usb2_phy_config;
+
+struct samsung_usb2_phy_instance {
+ const struct samsung_usb2_common_phy *cfg;
+ struct phy *phy;
+ struct samsung_usb2_phy_driver *drv;
+ int int_cnt;
+ int ext_cnt;
+};
+
+struct samsung_usb2_phy_driver {
+ const struct samsung_usb2_phy_config *cfg;
+ struct clk *clk;
+ struct clk *ref_clk;
+ struct regulator *vbus;
+ unsigned long ref_rate;
+ u32 ref_reg_val;
+ struct device *dev;
+ void __iomem *reg_phy;
+ struct regmap *reg_pmu;
+ struct regmap *reg_sys;
+ spinlock_t lock;
+ struct samsung_usb2_phy_instance instances[0];
+};
+
+struct samsung_usb2_common_phy {
+ int (*power_on)(struct samsung_usb2_phy_instance *);
+ int (*power_off)(struct samsung_usb2_phy_instance *);
+ unsigned int id;
+ char *label;
+};
+
+
+struct samsung_usb2_phy_config {
+ const struct samsung_usb2_common_phy *phys;
+ int (*rate_to_clk)(unsigned long, u32 *);
+ unsigned int num_phys;
+ bool has_mode_switch;
+ bool has_refclk_sel;
+};
+
+extern const struct samsung_usb2_phy_config exynos3250_usb2_phy_config;
+extern const struct samsung_usb2_phy_config exynos4210_usb2_phy_config;
+extern const struct samsung_usb2_phy_config exynos4x12_usb2_phy_config;
+extern const struct samsung_usb2_phy_config exynos5250_usb2_phy_config;
+extern const struct samsung_usb2_phy_config s5pv210_usb2_phy_config;
+#endif
diff --git a/drivers/phy/st/Kconfig b/drivers/phy/st/Kconfig
new file mode 100644
index 000000000..609719bdf
--- /dev/null
+++ b/drivers/phy/st/Kconfig
@@ -0,0 +1,47 @@
+#
+# Phy drivers for STMicro platforms
+#
+config PHY_MIPHY28LP
+ tristate "STMicroelectronics MIPHY28LP PHY driver for STiH407"
+ depends on ARCH_STI
+ select GENERIC_PHY
+ help
+ Enable this to support the miphy transceiver (for SATA/PCIE/USB3)
+ that is part of STMicroelectronics STiH407 SoC.
+
+config PHY_ST_SPEAR1310_MIPHY
+ tristate "ST SPEAR1310-MIPHY driver"
+ select GENERIC_PHY
+ depends on MACH_SPEAR1310 || COMPILE_TEST
+ help
+ Support for ST SPEAr1310 MIPHY which can be used for PCIe and SATA.
+
+config PHY_ST_SPEAR1340_MIPHY
+ tristate "ST SPEAR1340-MIPHY driver"
+ select GENERIC_PHY
+ depends on MACH_SPEAR1340 || COMPILE_TEST
+ help
+ Support for ST SPEAr1340 MIPHY which can be used for PCIe and SATA.
+
+config PHY_STIH407_USB
+ tristate "STMicroelectronics USB2 picoPHY driver for STiH407 family"
+ depends on RESET_CONTROLLER
+ depends on ARCH_STI || COMPILE_TEST
+ select GENERIC_PHY
+ help
+ Enable this support to enable the picoPHY device used by USB2
+ and USB3 controllers on STMicroelectronics STiH407 SoC families.
+
+config PHY_STM32_USBPHYC
+ tristate "STMicroelectronics STM32 USB HS PHY Controller driver"
+ depends on ARCH_STM32 || COMPILE_TEST
+ select GENERIC_PHY
+ help
+ Enable this to support the High-Speed USB transceivers that are part
+ of some STMicroelectronics STM32 SoCs.
+
+ This driver controls the entire USB PHY block: the USB PHY controller
+ (USBPHYC) and the two 8-bit wide UTMI+ interfaces. First interface is
+ used by an HS USB Host controller, and the second one is shared
+ between an HS USB OTG controller and an HS USB Host controller,
+ selected by a USB switch.
diff --git a/drivers/phy/st/Makefile b/drivers/phy/st/Makefile
new file mode 100644
index 000000000..c0091ad1f
--- /dev/null
+++ b/drivers/phy/st/Makefile
@@ -0,0 +1,5 @@
+obj-$(CONFIG_PHY_MIPHY28LP) += phy-miphy28lp.o
+obj-$(CONFIG_PHY_ST_SPEAR1310_MIPHY) += phy-spear1310-miphy.o
+obj-$(CONFIG_PHY_ST_SPEAR1340_MIPHY) += phy-spear1340-miphy.o
+obj-$(CONFIG_PHY_STIH407_USB) += phy-stih407-usb.o
+obj-$(CONFIG_PHY_STM32_USBPHYC) += phy-stm32-usbphyc.o
diff --git a/drivers/phy/st/phy-miphy28lp.c b/drivers/phy/st/phy-miphy28lp.c
new file mode 100644
index 000000000..213e2e153
--- /dev/null
+++ b/drivers/phy/st/phy-miphy28lp.c
@@ -0,0 +1,1286 @@
+/*
+ * Copyright (C) 2014 STMicroelectronics
+ *
+ * STMicroelectronics PHY driver MiPHY28lp (for SoC STiH407).
+ *
+ * Author: Alexandre Torgue <alexandre.torgue@st.com>
+ *
+ * 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/platform_device.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_address.h>
+#include <linux/clk.h>
+#include <linux/phy/phy.h>
+#include <linux/delay.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#include <dt-bindings/phy/phy.h>
+
+/* MiPHY registers */
+#define MIPHY_CONF_RESET 0x00
+#define RST_APPLI_SW BIT(0)
+#define RST_CONF_SW BIT(1)
+#define RST_MACRO_SW BIT(2)
+
+#define MIPHY_RESET 0x01
+#define RST_PLL_SW BIT(0)
+#define RST_COMP_SW BIT(2)
+
+#define MIPHY_STATUS_1 0x02
+#define PHY_RDY BIT(0)
+#define HFC_RDY BIT(1)
+#define HFC_PLL BIT(2)
+
+#define MIPHY_CONTROL 0x04
+#define TERM_EN_SW BIT(2)
+#define DIS_LINK_RST BIT(3)
+#define AUTO_RST_RX BIT(4)
+#define PX_RX_POL BIT(5)
+
+#define MIPHY_BOUNDARY_SEL 0x0a
+#define TX_SEL BIT(6)
+#define SSC_SEL BIT(4)
+#define GENSEL_SEL BIT(0)
+
+#define MIPHY_BOUNDARY_1 0x0b
+#define MIPHY_BOUNDARY_2 0x0c
+#define SSC_EN_SW BIT(2)
+
+#define MIPHY_PLL_CLKREF_FREQ 0x0d
+#define MIPHY_SPEED 0x0e
+#define TX_SPDSEL_80DEC 0
+#define TX_SPDSEL_40DEC 1
+#define TX_SPDSEL_20DEC 2
+#define RX_SPDSEL_80DEC 0
+#define RX_SPDSEL_40DEC (1 << 2)
+#define RX_SPDSEL_20DEC (2 << 2)
+
+#define MIPHY_CONF 0x0f
+#define MIPHY_CTRL_TEST_SEL 0x20
+#define MIPHY_CTRL_TEST_1 0x21
+#define MIPHY_CTRL_TEST_2 0x22
+#define MIPHY_CTRL_TEST_3 0x23
+#define MIPHY_CTRL_TEST_4 0x24
+#define MIPHY_FEEDBACK_TEST 0x25
+#define MIPHY_DEBUG_BUS 0x26
+#define MIPHY_DEBUG_STATUS_MSB 0x27
+#define MIPHY_DEBUG_STATUS_LSB 0x28
+#define MIPHY_PWR_RAIL_1 0x29
+#define MIPHY_PWR_RAIL_2 0x2a
+#define MIPHY_SYNCHAR_CONTROL 0x30
+
+#define MIPHY_COMP_FSM_1 0x3a
+#define COMP_START BIT(6)
+
+#define MIPHY_COMP_FSM_6 0x3f
+#define COMP_DONE BIT(7)
+
+#define MIPHY_COMP_POSTP 0x42
+#define MIPHY_TX_CTRL_1 0x49
+#define TX_REG_STEP_0V 0
+#define TX_REG_STEP_P_25MV 1
+#define TX_REG_STEP_P_50MV 2
+#define TX_REG_STEP_N_25MV 7
+#define TX_REG_STEP_N_50MV 6
+#define TX_REG_STEP_N_75MV 5
+
+#define MIPHY_TX_CTRL_2 0x4a
+#define TX_SLEW_SW_40_PS 0
+#define TX_SLEW_SW_80_PS 1
+#define TX_SLEW_SW_120_PS 2
+
+#define MIPHY_TX_CTRL_3 0x4b
+#define MIPHY_TX_CAL_MAN 0x4e
+#define TX_SLEW_CAL_MAN_EN BIT(0)
+
+#define MIPHY_TST_BIAS_BOOST_2 0x62
+#define MIPHY_BIAS_BOOST_1 0x63
+#define MIPHY_BIAS_BOOST_2 0x64
+#define MIPHY_RX_DESBUFF_FDB_2 0x67
+#define MIPHY_RX_DESBUFF_FDB_3 0x68
+#define MIPHY_SIGDET_COMPENS1 0x69
+#define MIPHY_SIGDET_COMPENS2 0x6a
+#define MIPHY_JITTER_PERIOD 0x6b
+#define MIPHY_JITTER_AMPLITUDE_1 0x6c
+#define MIPHY_JITTER_AMPLITUDE_2 0x6d
+#define MIPHY_JITTER_AMPLITUDE_3 0x6e
+#define MIPHY_RX_K_GAIN 0x78
+#define MIPHY_RX_BUFFER_CTRL 0x7a
+#define VGA_GAIN BIT(0)
+#define EQ_DC_GAIN BIT(2)
+#define EQ_BOOST_GAIN BIT(3)
+
+#define MIPHY_RX_VGA_GAIN 0x7b
+#define MIPHY_RX_EQU_GAIN_1 0x7f
+#define MIPHY_RX_EQU_GAIN_2 0x80
+#define MIPHY_RX_EQU_GAIN_3 0x81
+#define MIPHY_RX_CAL_CTRL_1 0x97
+#define MIPHY_RX_CAL_CTRL_2 0x98
+
+#define MIPHY_RX_CAL_OFFSET_CTRL 0x99
+#define CAL_OFFSET_VGA_64 (0x03 << 0)
+#define CAL_OFFSET_THRESHOLD_64 (0x03 << 2)
+#define VGA_OFFSET_POLARITY BIT(4)
+#define OFFSET_COMPENSATION_EN BIT(6)
+
+#define MIPHY_RX_CAL_VGA_STEP 0x9a
+#define MIPHY_RX_CAL_EYE_MIN 0x9d
+#define MIPHY_RX_CAL_OPT_LENGTH 0x9f
+#define MIPHY_RX_LOCK_CTRL_1 0xc1
+#define MIPHY_RX_LOCK_SETTINGS_OPT 0xc2
+#define MIPHY_RX_LOCK_STEP 0xc4
+
+#define MIPHY_RX_SIGDET_SLEEP_OA 0xc9
+#define MIPHY_RX_SIGDET_SLEEP_SEL 0xca
+#define MIPHY_RX_SIGDET_WAIT_SEL 0xcb
+#define MIPHY_RX_SIGDET_DATA_SEL 0xcc
+#define EN_ULTRA_LOW_POWER BIT(0)
+#define EN_FIRST_HALF BIT(1)
+#define EN_SECOND_HALF BIT(2)
+#define EN_DIGIT_SIGNAL_CHECK BIT(3)
+
+#define MIPHY_RX_POWER_CTRL_1 0xcd
+#define MIPHY_RX_POWER_CTRL_2 0xce
+#define MIPHY_PLL_CALSET_CTRL 0xd3
+#define MIPHY_PLL_CALSET_1 0xd4
+#define MIPHY_PLL_CALSET_2 0xd5
+#define MIPHY_PLL_CALSET_3 0xd6
+#define MIPHY_PLL_CALSET_4 0xd7
+#define MIPHY_PLL_SBR_1 0xe3
+#define SET_NEW_CHANGE BIT(1)
+
+#define MIPHY_PLL_SBR_2 0xe4
+#define MIPHY_PLL_SBR_3 0xe5
+#define MIPHY_PLL_SBR_4 0xe6
+#define MIPHY_PLL_COMMON_MISC_2 0xe9
+#define START_ACT_FILT BIT(6)
+
+#define MIPHY_PLL_SPAREIN 0xeb
+
+/*
+ * On STiH407 the glue logic can be different among MiPHY devices; for example:
+ * MiPHY0: OSC_FORCE_EXT means:
+ * 0: 30MHz crystal clk - 1: 100MHz ext clk routed through MiPHY1
+ * MiPHY1: OSC_FORCE_EXT means:
+ * 1: 30MHz crystal clk - 0: 100MHz ext clk routed through MiPHY1
+ * Some devices have not the possibility to check if the osc is ready.
+ */
+#define MIPHY_OSC_FORCE_EXT BIT(3)
+#define MIPHY_OSC_RDY BIT(5)
+
+#define MIPHY_CTRL_MASK 0x0f
+#define MIPHY_CTRL_DEFAULT 0
+#define MIPHY_CTRL_SYNC_D_EN BIT(2)
+
+/* SATA / PCIe defines */
+#define SATA_CTRL_MASK 0x07
+#define PCIE_CTRL_MASK 0xff
+#define SATA_CTRL_SELECT_SATA 1
+#define SATA_CTRL_SELECT_PCIE 0
+#define SYSCFG_PCIE_PCIE_VAL 0x80
+#define SATA_SPDMODE 1
+
+#define MIPHY_SATA_BANK_NB 3
+#define MIPHY_PCIE_BANK_NB 2
+
+enum {
+ SYSCFG_CTRL,
+ SYSCFG_STATUS,
+ SYSCFG_PCI,
+ SYSCFG_SATA,
+ SYSCFG_REG_MAX,
+};
+
+struct miphy28lp_phy {
+ struct phy *phy;
+ struct miphy28lp_dev *phydev;
+ void __iomem *base;
+ void __iomem *pipebase;
+
+ bool osc_force_ext;
+ bool osc_rdy;
+ bool px_rx_pol_inv;
+ bool ssc;
+ bool tx_impedance;
+
+ struct reset_control *miphy_rst;
+
+ u32 sata_gen;
+
+ /* Sysconfig registers offsets needed to configure the device */
+ u32 syscfg_reg[SYSCFG_REG_MAX];
+ u8 type;
+};
+
+struct miphy28lp_dev {
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex miphy_mutex;
+ struct miphy28lp_phy **phys;
+ int nphys;
+};
+
+struct miphy_initval {
+ u16 reg;
+ u16 val;
+};
+
+enum miphy_sata_gen { SATA_GEN1, SATA_GEN2, SATA_GEN3 };
+
+static char *PHY_TYPE_name[] = { "sata-up", "pcie-up", "", "usb3-up" };
+
+struct pll_ratio {
+ int clk_ref;
+ int calset_1;
+ int calset_2;
+ int calset_3;
+ int calset_4;
+ int cal_ctrl;
+};
+
+static struct pll_ratio sata_pll_ratio = {
+ .clk_ref = 0x1e,
+ .calset_1 = 0xc8,
+ .calset_2 = 0x00,
+ .calset_3 = 0x00,
+ .calset_4 = 0x00,
+ .cal_ctrl = 0x00,
+};
+
+static struct pll_ratio pcie_pll_ratio = {
+ .clk_ref = 0x1e,
+ .calset_1 = 0xa6,
+ .calset_2 = 0xaa,
+ .calset_3 = 0xaa,
+ .calset_4 = 0x00,
+ .cal_ctrl = 0x00,
+};
+
+static struct pll_ratio usb3_pll_ratio = {
+ .clk_ref = 0x1e,
+ .calset_1 = 0xa6,
+ .calset_2 = 0xaa,
+ .calset_3 = 0xaa,
+ .calset_4 = 0x04,
+ .cal_ctrl = 0x00,
+};
+
+struct miphy28lp_pll_gen {
+ int bank;
+ int speed;
+ int bias_boost_1;
+ int bias_boost_2;
+ int tx_ctrl_1;
+ int tx_ctrl_2;
+ int tx_ctrl_3;
+ int rx_k_gain;
+ int rx_vga_gain;
+ int rx_equ_gain_1;
+ int rx_equ_gain_2;
+ int rx_equ_gain_3;
+ int rx_buff_ctrl;
+};
+
+static struct miphy28lp_pll_gen sata_pll_gen[] = {
+ {
+ .bank = 0x00,
+ .speed = TX_SPDSEL_80DEC | RX_SPDSEL_80DEC,
+ .bias_boost_1 = 0x00,
+ .bias_boost_2 = 0xae,
+ .tx_ctrl_2 = 0x53,
+ .tx_ctrl_3 = 0x00,
+ .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN,
+ .rx_vga_gain = 0x00,
+ .rx_equ_gain_1 = 0x7d,
+ .rx_equ_gain_2 = 0x56,
+ .rx_equ_gain_3 = 0x00,
+ },
+ {
+ .bank = 0x01,
+ .speed = TX_SPDSEL_40DEC | RX_SPDSEL_40DEC,
+ .bias_boost_1 = 0x00,
+ .bias_boost_2 = 0xae,
+ .tx_ctrl_2 = 0x72,
+ .tx_ctrl_3 = 0x20,
+ .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN,
+ .rx_vga_gain = 0x00,
+ .rx_equ_gain_1 = 0x7d,
+ .rx_equ_gain_2 = 0x56,
+ .rx_equ_gain_3 = 0x00,
+ },
+ {
+ .bank = 0x02,
+ .speed = TX_SPDSEL_20DEC | RX_SPDSEL_20DEC,
+ .bias_boost_1 = 0x00,
+ .bias_boost_2 = 0xae,
+ .tx_ctrl_2 = 0xc0,
+ .tx_ctrl_3 = 0x20,
+ .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN,
+ .rx_vga_gain = 0x00,
+ .rx_equ_gain_1 = 0x7d,
+ .rx_equ_gain_2 = 0x56,
+ .rx_equ_gain_3 = 0x00,
+ },
+};
+
+static struct miphy28lp_pll_gen pcie_pll_gen[] = {
+ {
+ .bank = 0x00,
+ .speed = TX_SPDSEL_40DEC | RX_SPDSEL_40DEC,
+ .bias_boost_1 = 0x00,
+ .bias_boost_2 = 0xa5,
+ .tx_ctrl_1 = TX_REG_STEP_N_25MV,
+ .tx_ctrl_2 = 0x71,
+ .tx_ctrl_3 = 0x60,
+ .rx_k_gain = 0x98,
+ .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN,
+ .rx_vga_gain = 0x00,
+ .rx_equ_gain_1 = 0x79,
+ .rx_equ_gain_2 = 0x56,
+ },
+ {
+ .bank = 0x01,
+ .speed = TX_SPDSEL_20DEC | RX_SPDSEL_20DEC,
+ .bias_boost_1 = 0x00,
+ .bias_boost_2 = 0xa5,
+ .tx_ctrl_1 = TX_REG_STEP_N_25MV,
+ .tx_ctrl_2 = 0x70,
+ .tx_ctrl_3 = 0x60,
+ .rx_k_gain = 0xcc,
+ .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN,
+ .rx_vga_gain = 0x00,
+ .rx_equ_gain_1 = 0x78,
+ .rx_equ_gain_2 = 0x07,
+ },
+};
+
+static inline void miphy28lp_set_reset(struct miphy28lp_phy *miphy_phy)
+{
+ void __iomem *base = miphy_phy->base;
+ u8 val;
+
+ /* Putting Macro in reset */
+ writeb_relaxed(RST_APPLI_SW, base + MIPHY_CONF_RESET);
+
+ val = RST_APPLI_SW | RST_CONF_SW;
+ writeb_relaxed(val, base + MIPHY_CONF_RESET);
+
+ writeb_relaxed(RST_APPLI_SW, base + MIPHY_CONF_RESET);
+
+ /* Bringing the MIPHY-CPU registers out of reset */
+ if (miphy_phy->type == PHY_TYPE_PCIE) {
+ val = AUTO_RST_RX | TERM_EN_SW;
+ writeb_relaxed(val, base + MIPHY_CONTROL);
+ } else {
+ val = AUTO_RST_RX | TERM_EN_SW | DIS_LINK_RST;
+ writeb_relaxed(val, base + MIPHY_CONTROL);
+ }
+}
+
+static inline void miphy28lp_pll_calibration(struct miphy28lp_phy *miphy_phy,
+ struct pll_ratio *pll_ratio)
+{
+ void __iomem *base = miphy_phy->base;
+ u8 val;
+
+ /* Applying PLL Settings */
+ writeb_relaxed(0x1d, base + MIPHY_PLL_SPAREIN);
+ writeb_relaxed(pll_ratio->clk_ref, base + MIPHY_PLL_CLKREF_FREQ);
+
+ /* PLL Ratio */
+ writeb_relaxed(pll_ratio->calset_1, base + MIPHY_PLL_CALSET_1);
+ writeb_relaxed(pll_ratio->calset_2, base + MIPHY_PLL_CALSET_2);
+ writeb_relaxed(pll_ratio->calset_3, base + MIPHY_PLL_CALSET_3);
+ writeb_relaxed(pll_ratio->calset_4, base + MIPHY_PLL_CALSET_4);
+ writeb_relaxed(pll_ratio->cal_ctrl, base + MIPHY_PLL_CALSET_CTRL);
+
+ writeb_relaxed(TX_SEL, base + MIPHY_BOUNDARY_SEL);
+
+ val = (0x68 << 1) | TX_SLEW_CAL_MAN_EN;
+ writeb_relaxed(val, base + MIPHY_TX_CAL_MAN);
+
+ val = VGA_OFFSET_POLARITY | CAL_OFFSET_THRESHOLD_64 | CAL_OFFSET_VGA_64;
+
+ if (miphy_phy->type != PHY_TYPE_SATA)
+ val |= OFFSET_COMPENSATION_EN;
+
+ writeb_relaxed(val, base + MIPHY_RX_CAL_OFFSET_CTRL);
+
+ if (miphy_phy->type == PHY_TYPE_USB3) {
+ writeb_relaxed(0x00, base + MIPHY_CONF);
+ writeb_relaxed(0x70, base + MIPHY_RX_LOCK_STEP);
+ writeb_relaxed(EN_FIRST_HALF, base + MIPHY_RX_SIGDET_SLEEP_OA);
+ writeb_relaxed(EN_FIRST_HALF, base + MIPHY_RX_SIGDET_SLEEP_SEL);
+ writeb_relaxed(EN_FIRST_HALF, base + MIPHY_RX_SIGDET_WAIT_SEL);
+
+ val = EN_DIGIT_SIGNAL_CHECK | EN_FIRST_HALF;
+ writeb_relaxed(val, base + MIPHY_RX_SIGDET_DATA_SEL);
+ }
+
+}
+
+static inline void miphy28lp_sata_config_gen(struct miphy28lp_phy *miphy_phy)
+{
+ void __iomem *base = miphy_phy->base;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(sata_pll_gen); i++) {
+ struct miphy28lp_pll_gen *gen = &sata_pll_gen[i];
+
+ /* Banked settings */
+ writeb_relaxed(gen->bank, base + MIPHY_CONF);
+ writeb_relaxed(gen->speed, base + MIPHY_SPEED);
+ writeb_relaxed(gen->bias_boost_1, base + MIPHY_BIAS_BOOST_1);
+ writeb_relaxed(gen->bias_boost_2, base + MIPHY_BIAS_BOOST_2);
+
+ /* TX buffer Settings */
+ writeb_relaxed(gen->tx_ctrl_2, base + MIPHY_TX_CTRL_2);
+ writeb_relaxed(gen->tx_ctrl_3, base + MIPHY_TX_CTRL_3);
+
+ /* RX Buffer Settings */
+ writeb_relaxed(gen->rx_buff_ctrl, base + MIPHY_RX_BUFFER_CTRL);
+ writeb_relaxed(gen->rx_vga_gain, base + MIPHY_RX_VGA_GAIN);
+ writeb_relaxed(gen->rx_equ_gain_1, base + MIPHY_RX_EQU_GAIN_1);
+ writeb_relaxed(gen->rx_equ_gain_2, base + MIPHY_RX_EQU_GAIN_2);
+ writeb_relaxed(gen->rx_equ_gain_3, base + MIPHY_RX_EQU_GAIN_3);
+ }
+}
+
+static inline void miphy28lp_pcie_config_gen(struct miphy28lp_phy *miphy_phy)
+{
+ void __iomem *base = miphy_phy->base;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pcie_pll_gen); i++) {
+ struct miphy28lp_pll_gen *gen = &pcie_pll_gen[i];
+
+ /* Banked settings */
+ writeb_relaxed(gen->bank, base + MIPHY_CONF);
+ writeb_relaxed(gen->speed, base + MIPHY_SPEED);
+ writeb_relaxed(gen->bias_boost_1, base + MIPHY_BIAS_BOOST_1);
+ writeb_relaxed(gen->bias_boost_2, base + MIPHY_BIAS_BOOST_2);
+
+ /* TX buffer Settings */
+ writeb_relaxed(gen->tx_ctrl_1, base + MIPHY_TX_CTRL_1);
+ writeb_relaxed(gen->tx_ctrl_2, base + MIPHY_TX_CTRL_2);
+ writeb_relaxed(gen->tx_ctrl_3, base + MIPHY_TX_CTRL_3);
+
+ writeb_relaxed(gen->rx_k_gain, base + MIPHY_RX_K_GAIN);
+
+ /* RX Buffer Settings */
+ writeb_relaxed(gen->rx_buff_ctrl, base + MIPHY_RX_BUFFER_CTRL);
+ writeb_relaxed(gen->rx_vga_gain, base + MIPHY_RX_VGA_GAIN);
+ writeb_relaxed(gen->rx_equ_gain_1, base + MIPHY_RX_EQU_GAIN_1);
+ writeb_relaxed(gen->rx_equ_gain_2, base + MIPHY_RX_EQU_GAIN_2);
+ }
+}
+
+static inline int miphy28lp_wait_compensation(struct miphy28lp_phy *miphy_phy)
+{
+ unsigned long finish = jiffies + 5 * HZ;
+ u8 val;
+
+ /* Waiting for Compensation to complete */
+ do {
+ val = readb_relaxed(miphy_phy->base + MIPHY_COMP_FSM_6);
+
+ if (time_after_eq(jiffies, finish))
+ return -EBUSY;
+ cpu_relax();
+ } while (!(val & COMP_DONE));
+
+ return 0;
+}
+
+
+static inline int miphy28lp_compensation(struct miphy28lp_phy *miphy_phy,
+ struct pll_ratio *pll_ratio)
+{
+ void __iomem *base = miphy_phy->base;
+
+ /* Poll for HFC ready after reset release */
+ /* Compensation measurement */
+ writeb_relaxed(RST_PLL_SW | RST_COMP_SW, base + MIPHY_RESET);
+
+ writeb_relaxed(0x00, base + MIPHY_PLL_COMMON_MISC_2);
+ writeb_relaxed(pll_ratio->clk_ref, base + MIPHY_PLL_CLKREF_FREQ);
+ writeb_relaxed(COMP_START, base + MIPHY_COMP_FSM_1);
+
+ if (miphy_phy->type == PHY_TYPE_PCIE)
+ writeb_relaxed(RST_PLL_SW, base + MIPHY_RESET);
+
+ writeb_relaxed(0x00, base + MIPHY_RESET);
+ writeb_relaxed(START_ACT_FILT, base + MIPHY_PLL_COMMON_MISC_2);
+ writeb_relaxed(SET_NEW_CHANGE, base + MIPHY_PLL_SBR_1);
+
+ /* TX compensation offset to re-center TX impedance */
+ writeb_relaxed(0x00, base + MIPHY_COMP_POSTP);
+
+ if (miphy_phy->type == PHY_TYPE_PCIE)
+ return miphy28lp_wait_compensation(miphy_phy);
+
+ return 0;
+}
+
+static inline void miphy28_usb3_miphy_reset(struct miphy28lp_phy *miphy_phy)
+{
+ void __iomem *base = miphy_phy->base;
+ u8 val;
+
+ /* MIPHY Reset */
+ writeb_relaxed(RST_APPLI_SW, base + MIPHY_CONF_RESET);
+ writeb_relaxed(0x00, base + MIPHY_CONF_RESET);
+ writeb_relaxed(RST_COMP_SW, base + MIPHY_RESET);
+
+ val = RST_COMP_SW | RST_PLL_SW;
+ writeb_relaxed(val, base + MIPHY_RESET);
+
+ writeb_relaxed(0x00, base + MIPHY_PLL_COMMON_MISC_2);
+ writeb_relaxed(0x1e, base + MIPHY_PLL_CLKREF_FREQ);
+ writeb_relaxed(COMP_START, base + MIPHY_COMP_FSM_1);
+ writeb_relaxed(RST_PLL_SW, base + MIPHY_RESET);
+ writeb_relaxed(0x00, base + MIPHY_RESET);
+ writeb_relaxed(START_ACT_FILT, base + MIPHY_PLL_COMMON_MISC_2);
+ writeb_relaxed(0x00, base + MIPHY_CONF);
+ writeb_relaxed(0x00, base + MIPHY_BOUNDARY_1);
+ writeb_relaxed(0x00, base + MIPHY_TST_BIAS_BOOST_2);
+ writeb_relaxed(0x00, base + MIPHY_CONF);
+ writeb_relaxed(SET_NEW_CHANGE, base + MIPHY_PLL_SBR_1);
+ writeb_relaxed(0xa5, base + MIPHY_DEBUG_BUS);
+ writeb_relaxed(0x00, base + MIPHY_CONF);
+}
+
+static void miphy_sata_tune_ssc(struct miphy28lp_phy *miphy_phy)
+{
+ void __iomem *base = miphy_phy->base;
+ u8 val;
+
+ /* Compensate Tx impedance to avoid out of range values */
+ /*
+ * Enable the SSC on PLL for all banks
+ * SSC Modulation @ 31 KHz and 4000 ppm modulation amp
+ */
+ val = readb_relaxed(base + MIPHY_BOUNDARY_2);
+ val |= SSC_EN_SW;
+ writeb_relaxed(val, base + MIPHY_BOUNDARY_2);
+
+ val = readb_relaxed(base + MIPHY_BOUNDARY_SEL);
+ val |= SSC_SEL;
+ writeb_relaxed(val, base + MIPHY_BOUNDARY_SEL);
+
+ for (val = 0; val < MIPHY_SATA_BANK_NB; val++) {
+ writeb_relaxed(val, base + MIPHY_CONF);
+
+ /* Add value to each reference clock cycle */
+ /* and define the period length of the SSC */
+ writeb_relaxed(0x3c, base + MIPHY_PLL_SBR_2);
+ writeb_relaxed(0x6c, base + MIPHY_PLL_SBR_3);
+ writeb_relaxed(0x81, base + MIPHY_PLL_SBR_4);
+
+ /* Clear any previous request */
+ writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1);
+
+ /* requests the PLL to take in account new parameters */
+ writeb_relaxed(SET_NEW_CHANGE, base + MIPHY_PLL_SBR_1);
+
+ /* To be sure there is no other pending requests */
+ writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1);
+ }
+}
+
+static void miphy_pcie_tune_ssc(struct miphy28lp_phy *miphy_phy)
+{
+ void __iomem *base = miphy_phy->base;
+ u8 val;
+
+ /* Compensate Tx impedance to avoid out of range values */
+ /*
+ * Enable the SSC on PLL for all banks
+ * SSC Modulation @ 31 KHz and 4000 ppm modulation amp
+ */
+ val = readb_relaxed(base + MIPHY_BOUNDARY_2);
+ val |= SSC_EN_SW;
+ writeb_relaxed(val, base + MIPHY_BOUNDARY_2);
+
+ val = readb_relaxed(base + MIPHY_BOUNDARY_SEL);
+ val |= SSC_SEL;
+ writeb_relaxed(val, base + MIPHY_BOUNDARY_SEL);
+
+ for (val = 0; val < MIPHY_PCIE_BANK_NB; val++) {
+ writeb_relaxed(val, base + MIPHY_CONF);
+
+ /* Validate Step component */
+ writeb_relaxed(0x69, base + MIPHY_PLL_SBR_3);
+ writeb_relaxed(0x21, base + MIPHY_PLL_SBR_4);
+
+ /* Validate Period component */
+ writeb_relaxed(0x3c, base + MIPHY_PLL_SBR_2);
+ writeb_relaxed(0x21, base + MIPHY_PLL_SBR_4);
+
+ /* Clear any previous request */
+ writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1);
+
+ /* requests the PLL to take in account new parameters */
+ writeb_relaxed(SET_NEW_CHANGE, base + MIPHY_PLL_SBR_1);
+
+ /* To be sure there is no other pending requests */
+ writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1);
+ }
+}
+
+static inline void miphy_tune_tx_impedance(struct miphy28lp_phy *miphy_phy)
+{
+ /* Compensate Tx impedance to avoid out of range values */
+ writeb_relaxed(0x02, miphy_phy->base + MIPHY_COMP_POSTP);
+}
+
+static inline int miphy28lp_configure_sata(struct miphy28lp_phy *miphy_phy)
+{
+ void __iomem *base = miphy_phy->base;
+ int err;
+ u8 val;
+
+ /* Putting Macro in reset */
+ miphy28lp_set_reset(miphy_phy);
+
+ /* PLL calibration */
+ miphy28lp_pll_calibration(miphy_phy, &sata_pll_ratio);
+
+ /* Banked settings Gen1/Gen2/Gen3 */
+ miphy28lp_sata_config_gen(miphy_phy);
+
+ /* Power control */
+ /* Input bridge enable, manual input bridge control */
+ writeb_relaxed(0x21, base + MIPHY_RX_POWER_CTRL_1);
+
+ /* Macro out of reset */
+ writeb_relaxed(0x00, base + MIPHY_CONF_RESET);
+
+ /* Poll for HFC ready after reset release */
+ /* Compensation measurement */
+ err = miphy28lp_compensation(miphy_phy, &sata_pll_ratio);
+ if (err)
+ return err;
+
+ if (miphy_phy->px_rx_pol_inv) {
+ /* Invert Rx polarity */
+ val = readb_relaxed(miphy_phy->base + MIPHY_CONTROL);
+ val |= PX_RX_POL;
+ writeb_relaxed(val, miphy_phy->base + MIPHY_CONTROL);
+ }
+
+ if (miphy_phy->ssc)
+ miphy_sata_tune_ssc(miphy_phy);
+
+ if (miphy_phy->tx_impedance)
+ miphy_tune_tx_impedance(miphy_phy);
+
+ return 0;
+}
+
+static inline int miphy28lp_configure_pcie(struct miphy28lp_phy *miphy_phy)
+{
+ void __iomem *base = miphy_phy->base;
+ int err;
+
+ /* Putting Macro in reset */
+ miphy28lp_set_reset(miphy_phy);
+
+ /* PLL calibration */
+ miphy28lp_pll_calibration(miphy_phy, &pcie_pll_ratio);
+
+ /* Banked settings Gen1/Gen2 */
+ miphy28lp_pcie_config_gen(miphy_phy);
+
+ /* Power control */
+ /* Input bridge enable, manual input bridge control */
+ writeb_relaxed(0x21, base + MIPHY_RX_POWER_CTRL_1);
+
+ /* Macro out of reset */
+ writeb_relaxed(0x00, base + MIPHY_CONF_RESET);
+
+ /* Poll for HFC ready after reset release */
+ /* Compensation measurement */
+ err = miphy28lp_compensation(miphy_phy, &pcie_pll_ratio);
+ if (err)
+ return err;
+
+ if (miphy_phy->ssc)
+ miphy_pcie_tune_ssc(miphy_phy);
+
+ if (miphy_phy->tx_impedance)
+ miphy_tune_tx_impedance(miphy_phy);
+
+ return 0;
+}
+
+
+static inline void miphy28lp_configure_usb3(struct miphy28lp_phy *miphy_phy)
+{
+ void __iomem *base = miphy_phy->base;
+ u8 val;
+
+ /* Putting Macro in reset */
+ miphy28lp_set_reset(miphy_phy);
+
+ /* PLL calibration */
+ miphy28lp_pll_calibration(miphy_phy, &usb3_pll_ratio);
+
+ /* Writing The Speed Rate */
+ writeb_relaxed(0x00, base + MIPHY_CONF);
+
+ val = RX_SPDSEL_20DEC | TX_SPDSEL_20DEC;
+ writeb_relaxed(val, base + MIPHY_SPEED);
+
+ /* RX Channel compensation and calibration */
+ writeb_relaxed(0x1c, base + MIPHY_RX_LOCK_SETTINGS_OPT);
+ writeb_relaxed(0x51, base + MIPHY_RX_CAL_CTRL_1);
+ writeb_relaxed(0x70, base + MIPHY_RX_CAL_CTRL_2);
+
+ val = OFFSET_COMPENSATION_EN | VGA_OFFSET_POLARITY |
+ CAL_OFFSET_THRESHOLD_64 | CAL_OFFSET_VGA_64;
+ writeb_relaxed(val, base + MIPHY_RX_CAL_OFFSET_CTRL);
+ writeb_relaxed(0x22, base + MIPHY_RX_CAL_VGA_STEP);
+ writeb_relaxed(0x0e, base + MIPHY_RX_CAL_OPT_LENGTH);
+
+ val = EQ_DC_GAIN | VGA_GAIN;
+ writeb_relaxed(val, base + MIPHY_RX_BUFFER_CTRL);
+ writeb_relaxed(0x78, base + MIPHY_RX_EQU_GAIN_1);
+ writeb_relaxed(0x1b, base + MIPHY_SYNCHAR_CONTROL);
+
+ /* TX compensation offset to re-center TX impedance */
+ writeb_relaxed(0x02, base + MIPHY_COMP_POSTP);
+
+ /* Enable GENSEL_SEL and SSC */
+ /* TX_SEL=0 swing preemp forced by pipe registres */
+ val = SSC_SEL | GENSEL_SEL;
+ writeb_relaxed(val, base + MIPHY_BOUNDARY_SEL);
+
+ /* MIPHY Bias boost */
+ writeb_relaxed(0x00, base + MIPHY_BIAS_BOOST_1);
+ writeb_relaxed(0xa7, base + MIPHY_BIAS_BOOST_2);
+
+ /* SSC modulation */
+ writeb_relaxed(SSC_EN_SW, base + MIPHY_BOUNDARY_2);
+
+ /* MIPHY TX control */
+ writeb_relaxed(0x00, base + MIPHY_CONF);
+
+ /* Validate Step component */
+ writeb_relaxed(0x5a, base + MIPHY_PLL_SBR_3);
+ writeb_relaxed(0xa0, base + MIPHY_PLL_SBR_4);
+
+ /* Validate Period component */
+ writeb_relaxed(0x3c, base + MIPHY_PLL_SBR_2);
+ writeb_relaxed(0xa1, base + MIPHY_PLL_SBR_4);
+
+ /* Clear any previous request */
+ writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1);
+
+ /* requests the PLL to take in account new parameters */
+ writeb_relaxed(0x02, base + MIPHY_PLL_SBR_1);
+
+ /* To be sure there is no other pending requests */
+ writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1);
+
+ /* Rx PI controller settings */
+ writeb_relaxed(0xca, base + MIPHY_RX_K_GAIN);
+
+ /* MIPHY RX input bridge control */
+ /* INPUT_BRIDGE_EN_SW=1, manual input bridge control[0]=1 */
+ writeb_relaxed(0x21, base + MIPHY_RX_POWER_CTRL_1);
+ writeb_relaxed(0x29, base + MIPHY_RX_POWER_CTRL_1);
+ writeb_relaxed(0x1a, base + MIPHY_RX_POWER_CTRL_2);
+
+ /* MIPHY Reset for usb3 */
+ miphy28_usb3_miphy_reset(miphy_phy);
+}
+
+static inline int miphy_is_ready(struct miphy28lp_phy *miphy_phy)
+{
+ unsigned long finish = jiffies + 5 * HZ;
+ u8 mask = HFC_PLL | HFC_RDY;
+ u8 val;
+
+ /*
+ * For PCIe and USB3 check only that PLL and HFC are ready
+ * For SATA check also that phy is ready!
+ */
+ if (miphy_phy->type == PHY_TYPE_SATA)
+ mask |= PHY_RDY;
+
+ do {
+ val = readb_relaxed(miphy_phy->base + MIPHY_STATUS_1);
+ if ((val & mask) != mask)
+ cpu_relax();
+ else
+ return 0;
+ } while (!time_after_eq(jiffies, finish));
+
+ return -EBUSY;
+}
+
+static int miphy_osc_is_ready(struct miphy28lp_phy *miphy_phy)
+{
+ struct miphy28lp_dev *miphy_dev = miphy_phy->phydev;
+ unsigned long finish = jiffies + 5 * HZ;
+ u32 val;
+
+ if (!miphy_phy->osc_rdy)
+ return 0;
+
+ if (!miphy_phy->syscfg_reg[SYSCFG_STATUS])
+ return -EINVAL;
+
+ do {
+ regmap_read(miphy_dev->regmap,
+ miphy_phy->syscfg_reg[SYSCFG_STATUS], &val);
+
+ if ((val & MIPHY_OSC_RDY) != MIPHY_OSC_RDY)
+ cpu_relax();
+ else
+ return 0;
+ } while (!time_after_eq(jiffies, finish));
+
+ return -EBUSY;
+}
+
+static int miphy28lp_get_resource_byname(struct device_node *child,
+ char *rname, struct resource *res)
+{
+ int index;
+
+ index = of_property_match_string(child, "reg-names", rname);
+ if (index < 0)
+ return -ENODEV;
+
+ return of_address_to_resource(child, index, res);
+}
+
+static int miphy28lp_get_one_addr(struct device *dev,
+ struct device_node *child, char *rname,
+ void __iomem **base)
+{
+ struct resource res;
+ int ret;
+
+ ret = miphy28lp_get_resource_byname(child, rname, &res);
+ if (!ret) {
+ *base = devm_ioremap(dev, res.start, resource_size(&res));
+ if (!*base) {
+ dev_err(dev, "failed to ioremap %s address region\n"
+ , rname);
+ return -ENOENT;
+ }
+ }
+
+ return 0;
+}
+
+/* MiPHY reset and sysconf setup */
+static int miphy28lp_setup(struct miphy28lp_phy *miphy_phy, u32 miphy_val)
+{
+ int err;
+ struct miphy28lp_dev *miphy_dev = miphy_phy->phydev;
+
+ if (!miphy_phy->syscfg_reg[SYSCFG_CTRL])
+ return -EINVAL;
+
+ err = reset_control_assert(miphy_phy->miphy_rst);
+ if (err) {
+ dev_err(miphy_dev->dev, "unable to bring out of miphy reset\n");
+ return err;
+ }
+
+ if (miphy_phy->osc_force_ext)
+ miphy_val |= MIPHY_OSC_FORCE_EXT;
+
+ regmap_update_bits(miphy_dev->regmap,
+ miphy_phy->syscfg_reg[SYSCFG_CTRL],
+ MIPHY_CTRL_MASK, miphy_val);
+
+ err = reset_control_deassert(miphy_phy->miphy_rst);
+ if (err) {
+ dev_err(miphy_dev->dev, "unable to bring out of miphy reset\n");
+ return err;
+ }
+
+ return miphy_osc_is_ready(miphy_phy);
+}
+
+static int miphy28lp_init_sata(struct miphy28lp_phy *miphy_phy)
+{
+ struct miphy28lp_dev *miphy_dev = miphy_phy->phydev;
+ int err, sata_conf = SATA_CTRL_SELECT_SATA;
+
+ if ((!miphy_phy->syscfg_reg[SYSCFG_SATA]) ||
+ (!miphy_phy->syscfg_reg[SYSCFG_PCI]) ||
+ (!miphy_phy->base))
+ return -EINVAL;
+
+ dev_info(miphy_dev->dev, "sata-up mode, addr 0x%p\n", miphy_phy->base);
+
+ /* Configure the glue-logic */
+ sata_conf |= ((miphy_phy->sata_gen - SATA_GEN1) << SATA_SPDMODE);
+
+ regmap_update_bits(miphy_dev->regmap,
+ miphy_phy->syscfg_reg[SYSCFG_SATA],
+ SATA_CTRL_MASK, sata_conf);
+
+ regmap_update_bits(miphy_dev->regmap, miphy_phy->syscfg_reg[SYSCFG_PCI],
+ PCIE_CTRL_MASK, SATA_CTRL_SELECT_PCIE);
+
+ /* MiPHY path and clocking init */
+ err = miphy28lp_setup(miphy_phy, MIPHY_CTRL_DEFAULT);
+
+ if (err) {
+ dev_err(miphy_dev->dev, "SATA phy setup failed\n");
+ return err;
+ }
+
+ /* initialize miphy */
+ miphy28lp_configure_sata(miphy_phy);
+
+ return miphy_is_ready(miphy_phy);
+}
+
+static int miphy28lp_init_pcie(struct miphy28lp_phy *miphy_phy)
+{
+ struct miphy28lp_dev *miphy_dev = miphy_phy->phydev;
+ int err;
+
+ if ((!miphy_phy->syscfg_reg[SYSCFG_SATA]) ||
+ (!miphy_phy->syscfg_reg[SYSCFG_PCI])
+ || (!miphy_phy->base) || (!miphy_phy->pipebase))
+ return -EINVAL;
+
+ dev_info(miphy_dev->dev, "pcie-up mode, addr 0x%p\n", miphy_phy->base);
+
+ /* Configure the glue-logic */
+ regmap_update_bits(miphy_dev->regmap,
+ miphy_phy->syscfg_reg[SYSCFG_SATA],
+ SATA_CTRL_MASK, SATA_CTRL_SELECT_PCIE);
+
+ regmap_update_bits(miphy_dev->regmap, miphy_phy->syscfg_reg[SYSCFG_PCI],
+ PCIE_CTRL_MASK, SYSCFG_PCIE_PCIE_VAL);
+
+ /* MiPHY path and clocking init */
+ err = miphy28lp_setup(miphy_phy, MIPHY_CTRL_DEFAULT);
+
+ if (err) {
+ dev_err(miphy_dev->dev, "PCIe phy setup failed\n");
+ return err;
+ }
+
+ /* initialize miphy */
+ err = miphy28lp_configure_pcie(miphy_phy);
+ if (err)
+ return err;
+
+ /* PIPE Wrapper Configuration */
+ writeb_relaxed(0x68, miphy_phy->pipebase + 0x104); /* Rise_0 */
+ writeb_relaxed(0x61, miphy_phy->pipebase + 0x105); /* Rise_1 */
+ writeb_relaxed(0x68, miphy_phy->pipebase + 0x108); /* Fall_0 */
+ writeb_relaxed(0x61, miphy_phy->pipebase + 0x109); /* Fall-1 */
+ writeb_relaxed(0x68, miphy_phy->pipebase + 0x10c); /* Threshold_0 */
+ writeb_relaxed(0x60, miphy_phy->pipebase + 0x10d); /* Threshold_1 */
+
+ /* Wait for phy_ready */
+ return miphy_is_ready(miphy_phy);
+}
+
+static int miphy28lp_init_usb3(struct miphy28lp_phy *miphy_phy)
+{
+ struct miphy28lp_dev *miphy_dev = miphy_phy->phydev;
+ int err;
+
+ if ((!miphy_phy->base) || (!miphy_phy->pipebase))
+ return -EINVAL;
+
+ dev_info(miphy_dev->dev, "usb3-up mode, addr 0x%p\n", miphy_phy->base);
+
+ /* MiPHY path and clocking init */
+ err = miphy28lp_setup(miphy_phy, MIPHY_CTRL_SYNC_D_EN);
+ if (err) {
+ dev_err(miphy_dev->dev, "USB3 phy setup failed\n");
+ return err;
+ }
+
+ /* initialize miphy */
+ miphy28lp_configure_usb3(miphy_phy);
+
+ /* PIPE Wrapper Configuration */
+ writeb_relaxed(0x68, miphy_phy->pipebase + 0x23);
+ writeb_relaxed(0x61, miphy_phy->pipebase + 0x24);
+ writeb_relaxed(0x68, miphy_phy->pipebase + 0x26);
+ writeb_relaxed(0x61, miphy_phy->pipebase + 0x27);
+ writeb_relaxed(0x18, miphy_phy->pipebase + 0x29);
+ writeb_relaxed(0x61, miphy_phy->pipebase + 0x2a);
+
+ /* pipe Wrapper usb3 TX swing de-emph margin PREEMPH[7:4], SWING[3:0] */
+ writeb_relaxed(0X67, miphy_phy->pipebase + 0x68);
+ writeb_relaxed(0x0d, miphy_phy->pipebase + 0x69);
+ writeb_relaxed(0X67, miphy_phy->pipebase + 0x6a);
+ writeb_relaxed(0X0d, miphy_phy->pipebase + 0x6b);
+ writeb_relaxed(0X67, miphy_phy->pipebase + 0x6c);
+ writeb_relaxed(0X0d, miphy_phy->pipebase + 0x6d);
+ writeb_relaxed(0X67, miphy_phy->pipebase + 0x6e);
+ writeb_relaxed(0X0d, miphy_phy->pipebase + 0x6f);
+
+ return miphy_is_ready(miphy_phy);
+}
+
+static int miphy28lp_init(struct phy *phy)
+{
+ struct miphy28lp_phy *miphy_phy = phy_get_drvdata(phy);
+ struct miphy28lp_dev *miphy_dev = miphy_phy->phydev;
+ int ret;
+
+ mutex_lock(&miphy_dev->miphy_mutex);
+
+ switch (miphy_phy->type) {
+
+ case PHY_TYPE_SATA:
+ ret = miphy28lp_init_sata(miphy_phy);
+ break;
+ case PHY_TYPE_PCIE:
+ ret = miphy28lp_init_pcie(miphy_phy);
+ break;
+ case PHY_TYPE_USB3:
+ ret = miphy28lp_init_usb3(miphy_phy);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ mutex_unlock(&miphy_dev->miphy_mutex);
+
+ return ret;
+}
+
+static int miphy28lp_get_addr(struct miphy28lp_phy *miphy_phy)
+{
+ struct miphy28lp_dev *miphy_dev = miphy_phy->phydev;
+ struct device_node *phynode = miphy_phy->phy->dev.of_node;
+ int err;
+
+ if ((miphy_phy->type != PHY_TYPE_SATA) &&
+ (miphy_phy->type != PHY_TYPE_PCIE) &&
+ (miphy_phy->type != PHY_TYPE_USB3)) {
+ return -EINVAL;
+ }
+
+ err = miphy28lp_get_one_addr(miphy_dev->dev, phynode,
+ PHY_TYPE_name[miphy_phy->type - PHY_TYPE_SATA],
+ &miphy_phy->base);
+ if (err)
+ return err;
+
+ if ((miphy_phy->type == PHY_TYPE_PCIE) ||
+ (miphy_phy->type == PHY_TYPE_USB3)) {
+ err = miphy28lp_get_one_addr(miphy_dev->dev, phynode, "pipew",
+ &miphy_phy->pipebase);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static struct phy *miphy28lp_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct miphy28lp_dev *miphy_dev = dev_get_drvdata(dev);
+ struct miphy28lp_phy *miphy_phy = NULL;
+ struct device_node *phynode = args->np;
+ int ret, index = 0;
+
+ if (args->args_count != 1) {
+ dev_err(dev, "Invalid number of cells in 'phy' property\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ for (index = 0; index < miphy_dev->nphys; index++)
+ if (phynode == miphy_dev->phys[index]->phy->dev.of_node) {
+ miphy_phy = miphy_dev->phys[index];
+ break;
+ }
+
+ if (!miphy_phy) {
+ dev_err(dev, "Failed to find appropriate phy\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ miphy_phy->type = args->args[0];
+
+ ret = miphy28lp_get_addr(miphy_phy);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ return miphy_phy->phy;
+}
+
+static const struct phy_ops miphy28lp_ops = {
+ .init = miphy28lp_init,
+ .owner = THIS_MODULE,
+};
+
+static int miphy28lp_probe_resets(struct device_node *node,
+ struct miphy28lp_phy *miphy_phy)
+{
+ struct miphy28lp_dev *miphy_dev = miphy_phy->phydev;
+ int err;
+
+ miphy_phy->miphy_rst =
+ of_reset_control_get_shared(node, "miphy-sw-rst");
+
+ if (IS_ERR(miphy_phy->miphy_rst)) {
+ dev_err(miphy_dev->dev,
+ "miphy soft reset control not defined\n");
+ return PTR_ERR(miphy_phy->miphy_rst);
+ }
+
+ err = reset_control_deassert(miphy_phy->miphy_rst);
+ if (err) {
+ dev_err(miphy_dev->dev, "unable to bring out of miphy reset\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static int miphy28lp_of_probe(struct device_node *np,
+ struct miphy28lp_phy *miphy_phy)
+{
+ int i;
+ u32 ctrlreg;
+
+ miphy_phy->osc_force_ext =
+ of_property_read_bool(np, "st,osc-force-ext");
+
+ miphy_phy->osc_rdy = of_property_read_bool(np, "st,osc-rdy");
+
+ miphy_phy->px_rx_pol_inv =
+ of_property_read_bool(np, "st,px_rx_pol_inv");
+
+ miphy_phy->ssc = of_property_read_bool(np, "st,ssc-on");
+
+ miphy_phy->tx_impedance =
+ of_property_read_bool(np, "st,tx-impedance-comp");
+
+ of_property_read_u32(np, "st,sata-gen", &miphy_phy->sata_gen);
+ if (!miphy_phy->sata_gen)
+ miphy_phy->sata_gen = SATA_GEN1;
+
+ for (i = 0; i < SYSCFG_REG_MAX; i++) {
+ if (!of_property_read_u32_index(np, "st,syscfg", i, &ctrlreg))
+ miphy_phy->syscfg_reg[i] = ctrlreg;
+ }
+
+ return 0;
+}
+
+static int miphy28lp_probe(struct platform_device *pdev)
+{
+ struct device_node *child, *np = pdev->dev.of_node;
+ struct miphy28lp_dev *miphy_dev;
+ struct phy_provider *provider;
+ struct phy *phy;
+ int ret, port = 0;
+
+ miphy_dev = devm_kzalloc(&pdev->dev, sizeof(*miphy_dev), GFP_KERNEL);
+ if (!miphy_dev)
+ return -ENOMEM;
+
+ miphy_dev->nphys = of_get_child_count(np);
+ miphy_dev->phys = devm_kcalloc(&pdev->dev, miphy_dev->nphys,
+ sizeof(*miphy_dev->phys), GFP_KERNEL);
+ if (!miphy_dev->phys)
+ return -ENOMEM;
+
+ miphy_dev->regmap = syscon_regmap_lookup_by_phandle(np, "st,syscfg");
+ if (IS_ERR(miphy_dev->regmap)) {
+ dev_err(miphy_dev->dev, "No syscfg phandle specified\n");
+ return PTR_ERR(miphy_dev->regmap);
+ }
+
+ miphy_dev->dev = &pdev->dev;
+
+ dev_set_drvdata(&pdev->dev, miphy_dev);
+
+ mutex_init(&miphy_dev->miphy_mutex);
+
+ for_each_child_of_node(np, child) {
+ struct miphy28lp_phy *miphy_phy;
+
+ miphy_phy = devm_kzalloc(&pdev->dev, sizeof(*miphy_phy),
+ GFP_KERNEL);
+ if (!miphy_phy) {
+ ret = -ENOMEM;
+ goto put_child;
+ }
+
+ miphy_dev->phys[port] = miphy_phy;
+
+ phy = devm_phy_create(&pdev->dev, child, &miphy28lp_ops);
+ if (IS_ERR(phy)) {
+ dev_err(&pdev->dev, "failed to create PHY\n");
+ ret = PTR_ERR(phy);
+ goto put_child;
+ }
+
+ miphy_dev->phys[port]->phy = phy;
+ miphy_dev->phys[port]->phydev = miphy_dev;
+
+ ret = miphy28lp_of_probe(child, miphy_phy);
+ if (ret)
+ goto put_child;
+
+ ret = miphy28lp_probe_resets(child, miphy_dev->phys[port]);
+ if (ret)
+ goto put_child;
+
+ phy_set_drvdata(phy, miphy_dev->phys[port]);
+ port++;
+
+ }
+
+ provider = devm_of_phy_provider_register(&pdev->dev, miphy28lp_xlate);
+ return PTR_ERR_OR_ZERO(provider);
+put_child:
+ of_node_put(child);
+ return ret;
+}
+
+static const struct of_device_id miphy28lp_of_match[] = {
+ {.compatible = "st,miphy28lp-phy", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, miphy28lp_of_match);
+
+static struct platform_driver miphy28lp_driver = {
+ .probe = miphy28lp_probe,
+ .driver = {
+ .name = "miphy28lp-phy",
+ .of_match_table = miphy28lp_of_match,
+ }
+};
+
+module_platform_driver(miphy28lp_driver);
+
+MODULE_AUTHOR("Alexandre Torgue <alexandre.torgue@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics miphy28lp driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/st/phy-spear1310-miphy.c b/drivers/phy/st/phy-spear1310-miphy.c
new file mode 100644
index 000000000..ed67e98e5
--- /dev/null
+++ b/drivers/phy/st/phy-spear1310-miphy.c
@@ -0,0 +1,261 @@
+/*
+ * ST SPEAr1310-miphy driver
+ *
+ * Copyright (C) 2014 ST Microelectronics
+ * Pratyush Anand <pratyush.anand@gmail.com>
+ * Mohit Kumar <mohit.kumar.dhaka@gmail.com>
+ *
+ * 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/bitops.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+
+/* SPEAr1310 Registers */
+#define SPEAR1310_PCIE_SATA_CFG 0x3A4
+ #define SPEAR1310_PCIE_SATA2_SEL_PCIE (0 << 31)
+ #define SPEAR1310_PCIE_SATA1_SEL_PCIE (0 << 30)
+ #define SPEAR1310_PCIE_SATA0_SEL_PCIE (0 << 29)
+ #define SPEAR1310_PCIE_SATA2_SEL_SATA BIT(31)
+ #define SPEAR1310_PCIE_SATA1_SEL_SATA BIT(30)
+ #define SPEAR1310_PCIE_SATA0_SEL_SATA BIT(29)
+ #define SPEAR1310_SATA2_CFG_TX_CLK_EN BIT(27)
+ #define SPEAR1310_SATA2_CFG_RX_CLK_EN BIT(26)
+ #define SPEAR1310_SATA2_CFG_POWERUP_RESET BIT(25)
+ #define SPEAR1310_SATA2_CFG_PM_CLK_EN BIT(24)
+ #define SPEAR1310_SATA1_CFG_TX_CLK_EN BIT(23)
+ #define SPEAR1310_SATA1_CFG_RX_CLK_EN BIT(22)
+ #define SPEAR1310_SATA1_CFG_POWERUP_RESET BIT(21)
+ #define SPEAR1310_SATA1_CFG_PM_CLK_EN BIT(20)
+ #define SPEAR1310_SATA0_CFG_TX_CLK_EN BIT(19)
+ #define SPEAR1310_SATA0_CFG_RX_CLK_EN BIT(18)
+ #define SPEAR1310_SATA0_CFG_POWERUP_RESET BIT(17)
+ #define SPEAR1310_SATA0_CFG_PM_CLK_EN BIT(16)
+ #define SPEAR1310_PCIE2_CFG_DEVICE_PRESENT BIT(11)
+ #define SPEAR1310_PCIE2_CFG_POWERUP_RESET BIT(10)
+ #define SPEAR1310_PCIE2_CFG_CORE_CLK_EN BIT(9)
+ #define SPEAR1310_PCIE2_CFG_AUX_CLK_EN BIT(8)
+ #define SPEAR1310_PCIE1_CFG_DEVICE_PRESENT BIT(7)
+ #define SPEAR1310_PCIE1_CFG_POWERUP_RESET BIT(6)
+ #define SPEAR1310_PCIE1_CFG_CORE_CLK_EN BIT(5)
+ #define SPEAR1310_PCIE1_CFG_AUX_CLK_EN BIT(4)
+ #define SPEAR1310_PCIE0_CFG_DEVICE_PRESENT BIT(3)
+ #define SPEAR1310_PCIE0_CFG_POWERUP_RESET BIT(2)
+ #define SPEAR1310_PCIE0_CFG_CORE_CLK_EN BIT(1)
+ #define SPEAR1310_PCIE0_CFG_AUX_CLK_EN BIT(0)
+
+ #define SPEAR1310_PCIE_CFG_MASK(x) ((0xF << (x * 4)) | BIT((x + 29)))
+ #define SPEAR1310_SATA_CFG_MASK(x) ((0xF << (x * 4 + 16)) | \
+ BIT((x + 29)))
+ #define SPEAR1310_PCIE_CFG_VAL(x) \
+ (SPEAR1310_PCIE_SATA##x##_SEL_PCIE | \
+ SPEAR1310_PCIE##x##_CFG_AUX_CLK_EN | \
+ SPEAR1310_PCIE##x##_CFG_CORE_CLK_EN | \
+ SPEAR1310_PCIE##x##_CFG_POWERUP_RESET | \
+ SPEAR1310_PCIE##x##_CFG_DEVICE_PRESENT)
+ #define SPEAR1310_SATA_CFG_VAL(x) \
+ (SPEAR1310_PCIE_SATA##x##_SEL_SATA | \
+ SPEAR1310_SATA##x##_CFG_PM_CLK_EN | \
+ SPEAR1310_SATA##x##_CFG_POWERUP_RESET | \
+ SPEAR1310_SATA##x##_CFG_RX_CLK_EN | \
+ SPEAR1310_SATA##x##_CFG_TX_CLK_EN)
+
+#define SPEAR1310_PCIE_MIPHY_CFG_1 0x3A8
+ #define SPEAR1310_MIPHY_DUAL_OSC_BYPASS_EXT BIT(31)
+ #define SPEAR1310_MIPHY_DUAL_CLK_REF_DIV2 BIT(28)
+ #define SPEAR1310_MIPHY_DUAL_PLL_RATIO_TOP(x) (x << 16)
+ #define SPEAR1310_MIPHY_SINGLE_OSC_BYPASS_EXT BIT(15)
+ #define SPEAR1310_MIPHY_SINGLE_CLK_REF_DIV2 BIT(12)
+ #define SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(x) (x << 0)
+ #define SPEAR1310_PCIE_SATA_MIPHY_CFG_SATA_MASK (0xFFFF)
+ #define SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE_MASK (0xFFFF << 16)
+ #define SPEAR1310_PCIE_SATA_MIPHY_CFG_SATA \
+ (SPEAR1310_MIPHY_DUAL_OSC_BYPASS_EXT | \
+ SPEAR1310_MIPHY_DUAL_CLK_REF_DIV2 | \
+ SPEAR1310_MIPHY_DUAL_PLL_RATIO_TOP(60) | \
+ SPEAR1310_MIPHY_SINGLE_OSC_BYPASS_EXT | \
+ SPEAR1310_MIPHY_SINGLE_CLK_REF_DIV2 | \
+ SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(60))
+ #define SPEAR1310_PCIE_SATA_MIPHY_CFG_SATA_25M_CRYSTAL_CLK \
+ (SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(120))
+ #define SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE \
+ (SPEAR1310_MIPHY_DUAL_OSC_BYPASS_EXT | \
+ SPEAR1310_MIPHY_DUAL_PLL_RATIO_TOP(25) | \
+ SPEAR1310_MIPHY_SINGLE_OSC_BYPASS_EXT | \
+ SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(25))
+
+#define SPEAR1310_PCIE_MIPHY_CFG_2 0x3AC
+
+enum spear1310_miphy_mode {
+ SATA,
+ PCIE,
+};
+
+struct spear1310_miphy_priv {
+ /* instance id of this phy */
+ u32 id;
+ /* phy mode: 0 for SATA 1 for PCIe */
+ enum spear1310_miphy_mode mode;
+ /* regmap for any soc specific misc registers */
+ struct regmap *misc;
+ /* phy struct pointer */
+ struct phy *phy;
+};
+
+static int spear1310_miphy_pcie_init(struct spear1310_miphy_priv *priv)
+{
+ u32 val;
+
+ regmap_update_bits(priv->misc, SPEAR1310_PCIE_MIPHY_CFG_1,
+ SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE_MASK,
+ SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE);
+
+ switch (priv->id) {
+ case 0:
+ val = SPEAR1310_PCIE_CFG_VAL(0);
+ break;
+ case 1:
+ val = SPEAR1310_PCIE_CFG_VAL(1);
+ break;
+ case 2:
+ val = SPEAR1310_PCIE_CFG_VAL(2);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_update_bits(priv->misc, SPEAR1310_PCIE_SATA_CFG,
+ SPEAR1310_PCIE_CFG_MASK(priv->id), val);
+
+ return 0;
+}
+
+static int spear1310_miphy_pcie_exit(struct spear1310_miphy_priv *priv)
+{
+ regmap_update_bits(priv->misc, SPEAR1310_PCIE_SATA_CFG,
+ SPEAR1310_PCIE_CFG_MASK(priv->id), 0);
+
+ regmap_update_bits(priv->misc, SPEAR1310_PCIE_MIPHY_CFG_1,
+ SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE_MASK, 0);
+
+ return 0;
+}
+
+static int spear1310_miphy_init(struct phy *phy)
+{
+ struct spear1310_miphy_priv *priv = phy_get_drvdata(phy);
+ int ret = 0;
+
+ if (priv->mode == PCIE)
+ ret = spear1310_miphy_pcie_init(priv);
+
+ return ret;
+}
+
+static int spear1310_miphy_exit(struct phy *phy)
+{
+ struct spear1310_miphy_priv *priv = phy_get_drvdata(phy);
+ int ret = 0;
+
+ if (priv->mode == PCIE)
+ ret = spear1310_miphy_pcie_exit(priv);
+
+ return ret;
+}
+
+static const struct of_device_id spear1310_miphy_of_match[] = {
+ { .compatible = "st,spear1310-miphy" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, spear1310_miphy_of_match);
+
+static const struct phy_ops spear1310_miphy_ops = {
+ .init = spear1310_miphy_init,
+ .exit = spear1310_miphy_exit,
+ .owner = THIS_MODULE,
+};
+
+static struct phy *spear1310_miphy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct spear1310_miphy_priv *priv = dev_get_drvdata(dev);
+
+ if (args->args_count < 1) {
+ dev_err(dev, "DT did not pass correct no of args\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ priv->mode = args->args[0];
+
+ if (priv->mode != SATA && priv->mode != PCIE) {
+ dev_err(dev, "DT did not pass correct phy mode\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ return priv->phy;
+}
+
+static int spear1310_miphy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct spear1310_miphy_priv *priv;
+ struct phy_provider *phy_provider;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->misc =
+ syscon_regmap_lookup_by_phandle(dev->of_node, "misc");
+ if (IS_ERR(priv->misc)) {
+ dev_err(dev, "failed to find misc regmap\n");
+ return PTR_ERR(priv->misc);
+ }
+
+ if (of_property_read_u32(dev->of_node, "phy-id", &priv->id)) {
+ dev_err(dev, "failed to find phy id\n");
+ return -EINVAL;
+ }
+
+ priv->phy = devm_phy_create(dev, NULL, &spear1310_miphy_ops);
+ if (IS_ERR(priv->phy)) {
+ dev_err(dev, "failed to create SATA PCIe PHY\n");
+ return PTR_ERR(priv->phy);
+ }
+
+ dev_set_drvdata(dev, priv);
+ phy_set_drvdata(priv->phy, priv);
+
+ phy_provider =
+ devm_of_phy_provider_register(dev, spear1310_miphy_xlate);
+ if (IS_ERR(phy_provider)) {
+ dev_err(dev, "failed to register phy provider\n");
+ return PTR_ERR(phy_provider);
+ }
+
+ return 0;
+}
+
+static struct platform_driver spear1310_miphy_driver = {
+ .probe = spear1310_miphy_probe,
+ .driver = {
+ .name = "spear1310-miphy",
+ .of_match_table = of_match_ptr(spear1310_miphy_of_match),
+ },
+};
+
+module_platform_driver(spear1310_miphy_driver);
+
+MODULE_DESCRIPTION("ST SPEAR1310-MIPHY driver");
+MODULE_AUTHOR("Pratyush Anand <pratyush.anand@gmail.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/st/phy-spear1340-miphy.c b/drivers/phy/st/phy-spear1340-miphy.c
new file mode 100644
index 000000000..97280c0cf
--- /dev/null
+++ b/drivers/phy/st/phy-spear1340-miphy.c
@@ -0,0 +1,294 @@
+/*
+ * ST spear1340-miphy driver
+ *
+ * Copyright (C) 2014 ST Microelectronics
+ * Pratyush Anand <pratyush.anand@gmail.com>
+ * Mohit Kumar <mohit.kumar.dhaka@gmail.com>
+ *
+ * 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/bitops.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+
+/* SPEAr1340 Registers */
+/* Power Management Registers */
+#define SPEAR1340_PCM_CFG 0x100
+ #define SPEAR1340_PCM_CFG_SATA_POWER_EN BIT(11)
+#define SPEAR1340_PCM_WKUP_CFG 0x104
+#define SPEAR1340_SWITCH_CTR 0x108
+
+#define SPEAR1340_PERIP1_SW_RST 0x318
+ #define SPEAR1340_PERIP1_SW_RSATA BIT(12)
+#define SPEAR1340_PERIP2_SW_RST 0x31C
+#define SPEAR1340_PERIP3_SW_RST 0x320
+
+/* PCIE - SATA configuration registers */
+#define SPEAR1340_PCIE_SATA_CFG 0x424
+ /* PCIE CFG MASks */
+ #define SPEAR1340_PCIE_CFG_DEVICE_PRESENT BIT(11)
+ #define SPEAR1340_PCIE_CFG_POWERUP_RESET BIT(10)
+ #define SPEAR1340_PCIE_CFG_CORE_CLK_EN BIT(9)
+ #define SPEAR1340_PCIE_CFG_AUX_CLK_EN BIT(8)
+ #define SPEAR1340_SATA_CFG_TX_CLK_EN BIT(4)
+ #define SPEAR1340_SATA_CFG_RX_CLK_EN BIT(3)
+ #define SPEAR1340_SATA_CFG_POWERUP_RESET BIT(2)
+ #define SPEAR1340_SATA_CFG_PM_CLK_EN BIT(1)
+ #define SPEAR1340_PCIE_SATA_SEL_PCIE (0)
+ #define SPEAR1340_PCIE_SATA_SEL_SATA (1)
+ #define SPEAR1340_PCIE_SATA_CFG_MASK 0xF1F
+ #define SPEAR1340_PCIE_CFG_VAL (SPEAR1340_PCIE_SATA_SEL_PCIE | \
+ SPEAR1340_PCIE_CFG_AUX_CLK_EN | \
+ SPEAR1340_PCIE_CFG_CORE_CLK_EN | \
+ SPEAR1340_PCIE_CFG_POWERUP_RESET | \
+ SPEAR1340_PCIE_CFG_DEVICE_PRESENT)
+ #define SPEAR1340_SATA_CFG_VAL (SPEAR1340_PCIE_SATA_SEL_SATA | \
+ SPEAR1340_SATA_CFG_PM_CLK_EN | \
+ SPEAR1340_SATA_CFG_POWERUP_RESET | \
+ SPEAR1340_SATA_CFG_RX_CLK_EN | \
+ SPEAR1340_SATA_CFG_TX_CLK_EN)
+
+#define SPEAR1340_PCIE_MIPHY_CFG 0x428
+ #define SPEAR1340_MIPHY_OSC_BYPASS_EXT BIT(31)
+ #define SPEAR1340_MIPHY_CLK_REF_DIV2 BIT(27)
+ #define SPEAR1340_MIPHY_CLK_REF_DIV4 (2 << 27)
+ #define SPEAR1340_MIPHY_CLK_REF_DIV8 (3 << 27)
+ #define SPEAR1340_MIPHY_PLL_RATIO_TOP(x) (x << 0)
+ #define SPEAR1340_PCIE_MIPHY_CFG_MASK 0xF80000FF
+ #define SPEAR1340_PCIE_SATA_MIPHY_CFG_SATA \
+ (SPEAR1340_MIPHY_OSC_BYPASS_EXT | \
+ SPEAR1340_MIPHY_CLK_REF_DIV2 | \
+ SPEAR1340_MIPHY_PLL_RATIO_TOP(60))
+ #define SPEAR1340_PCIE_SATA_MIPHY_CFG_SATA_25M_CRYSTAL_CLK \
+ (SPEAR1340_MIPHY_PLL_RATIO_TOP(120))
+ #define SPEAR1340_PCIE_SATA_MIPHY_CFG_PCIE \
+ (SPEAR1340_MIPHY_OSC_BYPASS_EXT | \
+ SPEAR1340_MIPHY_PLL_RATIO_TOP(25))
+
+enum spear1340_miphy_mode {
+ SATA,
+ PCIE,
+};
+
+struct spear1340_miphy_priv {
+ /* phy mode: 0 for SATA 1 for PCIe */
+ enum spear1340_miphy_mode mode;
+ /* regmap for any soc specific misc registers */
+ struct regmap *misc;
+ /* phy struct pointer */
+ struct phy *phy;
+};
+
+static int spear1340_miphy_sata_init(struct spear1340_miphy_priv *priv)
+{
+ regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG,
+ SPEAR1340_PCIE_SATA_CFG_MASK,
+ SPEAR1340_SATA_CFG_VAL);
+ regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG,
+ SPEAR1340_PCIE_MIPHY_CFG_MASK,
+ SPEAR1340_PCIE_SATA_MIPHY_CFG_SATA_25M_CRYSTAL_CLK);
+ /* Switch on sata power domain */
+ regmap_update_bits(priv->misc, SPEAR1340_PCM_CFG,
+ SPEAR1340_PCM_CFG_SATA_POWER_EN,
+ SPEAR1340_PCM_CFG_SATA_POWER_EN);
+ /* Wait for SATA power domain on */
+ msleep(20);
+
+ /* Disable PCIE SATA Controller reset */
+ regmap_update_bits(priv->misc, SPEAR1340_PERIP1_SW_RST,
+ SPEAR1340_PERIP1_SW_RSATA, 0);
+ /* Wait for SATA reset de-assert completion */
+ msleep(20);
+
+ return 0;
+}
+
+static int spear1340_miphy_sata_exit(struct spear1340_miphy_priv *priv)
+{
+ regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG,
+ SPEAR1340_PCIE_SATA_CFG_MASK, 0);
+ regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG,
+ SPEAR1340_PCIE_MIPHY_CFG_MASK, 0);
+
+ /* Enable PCIE SATA Controller reset */
+ regmap_update_bits(priv->misc, SPEAR1340_PERIP1_SW_RST,
+ SPEAR1340_PERIP1_SW_RSATA,
+ SPEAR1340_PERIP1_SW_RSATA);
+ /* Wait for SATA power domain off */
+ msleep(20);
+ /* Switch off sata power domain */
+ regmap_update_bits(priv->misc, SPEAR1340_PCM_CFG,
+ SPEAR1340_PCM_CFG_SATA_POWER_EN, 0);
+ /* Wait for SATA reset assert completion */
+ msleep(20);
+
+ return 0;
+}
+
+static int spear1340_miphy_pcie_init(struct spear1340_miphy_priv *priv)
+{
+ regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG,
+ SPEAR1340_PCIE_MIPHY_CFG_MASK,
+ SPEAR1340_PCIE_SATA_MIPHY_CFG_PCIE);
+ regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG,
+ SPEAR1340_PCIE_SATA_CFG_MASK,
+ SPEAR1340_PCIE_CFG_VAL);
+
+ return 0;
+}
+
+static int spear1340_miphy_pcie_exit(struct spear1340_miphy_priv *priv)
+{
+ regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG,
+ SPEAR1340_PCIE_MIPHY_CFG_MASK, 0);
+ regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG,
+ SPEAR1340_PCIE_SATA_CFG_MASK, 0);
+
+ return 0;
+}
+
+static int spear1340_miphy_init(struct phy *phy)
+{
+ struct spear1340_miphy_priv *priv = phy_get_drvdata(phy);
+ int ret = 0;
+
+ if (priv->mode == SATA)
+ ret = spear1340_miphy_sata_init(priv);
+ else if (priv->mode == PCIE)
+ ret = spear1340_miphy_pcie_init(priv);
+
+ return ret;
+}
+
+static int spear1340_miphy_exit(struct phy *phy)
+{
+ struct spear1340_miphy_priv *priv = phy_get_drvdata(phy);
+ int ret = 0;
+
+ if (priv->mode == SATA)
+ ret = spear1340_miphy_sata_exit(priv);
+ else if (priv->mode == PCIE)
+ ret = spear1340_miphy_pcie_exit(priv);
+
+ return ret;
+}
+
+static const struct of_device_id spear1340_miphy_of_match[] = {
+ { .compatible = "st,spear1340-miphy" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, spear1340_miphy_of_match);
+
+static const struct phy_ops spear1340_miphy_ops = {
+ .init = spear1340_miphy_init,
+ .exit = spear1340_miphy_exit,
+ .owner = THIS_MODULE,
+};
+
+#ifdef CONFIG_PM_SLEEP
+static int spear1340_miphy_suspend(struct device *dev)
+{
+ struct spear1340_miphy_priv *priv = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (priv->mode == SATA)
+ ret = spear1340_miphy_sata_exit(priv);
+
+ return ret;
+}
+
+static int spear1340_miphy_resume(struct device *dev)
+{
+ struct spear1340_miphy_priv *priv = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (priv->mode == SATA)
+ ret = spear1340_miphy_sata_init(priv);
+
+ return ret;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(spear1340_miphy_pm_ops, spear1340_miphy_suspend,
+ spear1340_miphy_resume);
+
+static struct phy *spear1340_miphy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct spear1340_miphy_priv *priv = dev_get_drvdata(dev);
+
+ if (args->args_count < 1) {
+ dev_err(dev, "DT did not pass correct no of args\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ priv->mode = args->args[0];
+
+ if (priv->mode != SATA && priv->mode != PCIE) {
+ dev_err(dev, "DT did not pass correct phy mode\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ return priv->phy;
+}
+
+static int spear1340_miphy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct spear1340_miphy_priv *priv;
+ struct phy_provider *phy_provider;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->misc =
+ syscon_regmap_lookup_by_phandle(dev->of_node, "misc");
+ if (IS_ERR(priv->misc)) {
+ dev_err(dev, "failed to find misc regmap\n");
+ return PTR_ERR(priv->misc);
+ }
+
+ priv->phy = devm_phy_create(dev, NULL, &spear1340_miphy_ops);
+ if (IS_ERR(priv->phy)) {
+ dev_err(dev, "failed to create SATA PCIe PHY\n");
+ return PTR_ERR(priv->phy);
+ }
+
+ dev_set_drvdata(dev, priv);
+ phy_set_drvdata(priv->phy, priv);
+
+ phy_provider =
+ devm_of_phy_provider_register(dev, spear1340_miphy_xlate);
+ if (IS_ERR(phy_provider)) {
+ dev_err(dev, "failed to register phy provider\n");
+ return PTR_ERR(phy_provider);
+ }
+
+ return 0;
+}
+
+static struct platform_driver spear1340_miphy_driver = {
+ .probe = spear1340_miphy_probe,
+ .driver = {
+ .name = "spear1340-miphy",
+ .pm = &spear1340_miphy_pm_ops,
+ .of_match_table = of_match_ptr(spear1340_miphy_of_match),
+ },
+};
+
+module_platform_driver(spear1340_miphy_driver);
+
+MODULE_DESCRIPTION("ST SPEAR1340-MIPHY driver");
+MODULE_AUTHOR("Pratyush Anand <pratyush.anand@gmail.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/st/phy-stih407-usb.c b/drivers/phy/st/phy-stih407-usb.c
new file mode 100644
index 000000000..b1f44ab66
--- /dev/null
+++ b/drivers/phy/st/phy-stih407-usb.c
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2014 STMicroelectronics
+ *
+ * STMicroelectronics Generic PHY driver for STiH407 USB2.
+ *
+ * Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+ *
+ * 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/platform_device.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/clk.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/mfd/syscon.h>
+#include <linux/phy/phy.h>
+
+#define PHYPARAM_REG 1
+#define PHYCTRL_REG 2
+
+/* Default PHY_SEL and REFCLKSEL configuration */
+#define STIH407_USB_PICOPHY_CTRL_PORT_CONF 0x6
+#define STIH407_USB_PICOPHY_CTRL_PORT_MASK 0x1f
+
+/* ports parameters overriding */
+#define STIH407_USB_PICOPHY_PARAM_DEF 0x39a4dc
+#define STIH407_USB_PICOPHY_PARAM_MASK 0xffffffff
+
+struct stih407_usb2_picophy {
+ struct phy *phy;
+ struct regmap *regmap;
+ struct device *dev;
+ struct reset_control *rstc;
+ struct reset_control *rstport;
+ int ctrl;
+ int param;
+};
+
+static int stih407_usb2_pico_ctrl(struct stih407_usb2_picophy *phy_dev)
+{
+ reset_control_deassert(phy_dev->rstc);
+
+ return regmap_update_bits(phy_dev->regmap, phy_dev->ctrl,
+ STIH407_USB_PICOPHY_CTRL_PORT_MASK,
+ STIH407_USB_PICOPHY_CTRL_PORT_CONF);
+}
+
+static int stih407_usb2_init_port(struct phy *phy)
+{
+ int ret;
+ struct stih407_usb2_picophy *phy_dev = phy_get_drvdata(phy);
+
+ stih407_usb2_pico_ctrl(phy_dev);
+
+ ret = regmap_update_bits(phy_dev->regmap,
+ phy_dev->param,
+ STIH407_USB_PICOPHY_PARAM_MASK,
+ STIH407_USB_PICOPHY_PARAM_DEF);
+ if (ret)
+ return ret;
+
+ return reset_control_deassert(phy_dev->rstport);
+}
+
+static int stih407_usb2_exit_port(struct phy *phy)
+{
+ struct stih407_usb2_picophy *phy_dev = phy_get_drvdata(phy);
+
+ /*
+ * Only port reset is asserted, phy global reset is kept untouched
+ * as other ports may still be active. When all ports are in reset
+ * state, assumption is made that power will be cut off on the phy, in
+ * case of suspend for instance. Theoretically, asserting individual
+ * reset (like here) or global reset should be equivalent.
+ */
+ return reset_control_assert(phy_dev->rstport);
+}
+
+static const struct phy_ops stih407_usb2_picophy_data = {
+ .init = stih407_usb2_init_port,
+ .exit = stih407_usb2_exit_port,
+ .owner = THIS_MODULE,
+};
+
+static int stih407_usb2_picophy_probe(struct platform_device *pdev)
+{
+ struct stih407_usb2_picophy *phy_dev;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct phy_provider *phy_provider;
+ struct phy *phy;
+ int ret;
+
+ phy_dev = devm_kzalloc(dev, sizeof(*phy_dev), GFP_KERNEL);
+ if (!phy_dev)
+ return -ENOMEM;
+
+ phy_dev->dev = dev;
+ dev_set_drvdata(dev, phy_dev);
+
+ phy_dev->rstc = devm_reset_control_get_shared(dev, "global");
+ if (IS_ERR(phy_dev->rstc)) {
+ dev_err(dev, "failed to ctrl picoPHY reset\n");
+ return PTR_ERR(phy_dev->rstc);
+ }
+
+ phy_dev->rstport = devm_reset_control_get_exclusive(dev, "port");
+ if (IS_ERR(phy_dev->rstport)) {
+ dev_err(dev, "failed to ctrl picoPHY reset\n");
+ return PTR_ERR(phy_dev->rstport);
+ }
+
+ /* Reset port by default: only deassert it in phy init */
+ reset_control_assert(phy_dev->rstport);
+
+ phy_dev->regmap = syscon_regmap_lookup_by_phandle(np, "st,syscfg");
+ if (IS_ERR(phy_dev->regmap)) {
+ dev_err(dev, "No syscfg phandle specified\n");
+ return PTR_ERR(phy_dev->regmap);
+ }
+
+ ret = of_property_read_u32_index(np, "st,syscfg", PHYPARAM_REG,
+ &phy_dev->param);
+ if (ret) {
+ dev_err(dev, "can't get phyparam offset (%d)\n", ret);
+ return ret;
+ }
+
+ ret = of_property_read_u32_index(np, "st,syscfg", PHYCTRL_REG,
+ &phy_dev->ctrl);
+ if (ret) {
+ dev_err(dev, "can't get phyctrl offset (%d)\n", ret);
+ return ret;
+ }
+
+ phy = devm_phy_create(dev, NULL, &stih407_usb2_picophy_data);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create Display Port PHY\n");
+ return PTR_ERR(phy);
+ }
+
+ phy_dev->phy = phy;
+ phy_set_drvdata(phy, phy_dev);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(phy_provider))
+ return PTR_ERR(phy_provider);
+
+ dev_info(dev, "STiH407 USB Generic picoPHY driver probed!");
+
+ return 0;
+}
+
+static const struct of_device_id stih407_usb2_picophy_of_match[] = {
+ { .compatible = "st,stih407-usb2-phy" },
+ { /*sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, stih407_usb2_picophy_of_match);
+
+static struct platform_driver stih407_usb2_picophy_driver = {
+ .probe = stih407_usb2_picophy_probe,
+ .driver = {
+ .name = "stih407-usb-genphy",
+ .of_match_table = stih407_usb2_picophy_of_match,
+ }
+};
+
+module_platform_driver(stih407_usb2_picophy_driver);
+
+MODULE_AUTHOR("Giuseppe Cavallaro <peppe.cavallaro@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics Generic picoPHY driver for STiH407");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/st/phy-stm32-usbphyc.c b/drivers/phy/st/phy-stm32-usbphyc.c
new file mode 100644
index 000000000..1255cd1d9
--- /dev/null
+++ b/drivers/phy/st/phy-stm32-usbphyc.c
@@ -0,0 +1,460 @@
+// SPDX-Licence-Identifier: GPL-2.0
+/*
+ * STMicroelectronics STM32 USB PHY Controller driver
+ *
+ * Copyright (C) 2018 STMicroelectronics
+ * Author(s): Amelie Delaunay <amelie.delaunay@st.com>.
+ */
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/phy/phy.h>
+#include <linux/reset.h>
+
+#define STM32_USBPHYC_PLL 0x0
+#define STM32_USBPHYC_MISC 0x8
+#define STM32_USBPHYC_VERSION 0x3F4
+
+/* STM32_USBPHYC_PLL bit fields */
+#define PLLNDIV GENMASK(6, 0)
+#define PLLFRACIN GENMASK(25, 10)
+#define PLLEN BIT(26)
+#define PLLSTRB BIT(27)
+#define PLLSTRBYP BIT(28)
+#define PLLFRACCTL BIT(29)
+#define PLLDITHEN0 BIT(30)
+#define PLLDITHEN1 BIT(31)
+
+/* STM32_USBPHYC_MISC bit fields */
+#define SWITHOST BIT(0)
+
+/* STM32_USBPHYC_VERSION bit fields */
+#define MINREV GENMASK(3, 0)
+#define MAJREV GENMASK(7, 4)
+
+static const char * const supplies_names[] = {
+ "vdda1v1", /* 1V1 */
+ "vdda1v8", /* 1V8 */
+};
+
+#define NUM_SUPPLIES ARRAY_SIZE(supplies_names)
+
+#define PLL_LOCK_TIME_US 100
+#define PLL_PWR_DOWN_TIME_US 5
+#define PLL_FVCO_MHZ 2880
+#define PLL_INFF_MIN_RATE_HZ 19200000
+#define PLL_INFF_MAX_RATE_HZ 38400000
+#define HZ_PER_MHZ 1000000L
+
+struct pll_params {
+ u8 ndiv;
+ u16 frac;
+};
+
+struct stm32_usbphyc_phy {
+ struct phy *phy;
+ struct stm32_usbphyc *usbphyc;
+ struct regulator_bulk_data supplies[NUM_SUPPLIES];
+ u32 index;
+ bool active;
+};
+
+struct stm32_usbphyc {
+ struct device *dev;
+ void __iomem *base;
+ struct clk *clk;
+ struct reset_control *rst;
+ struct stm32_usbphyc_phy **phys;
+ int nphys;
+ int switch_setup;
+};
+
+static inline void stm32_usbphyc_set_bits(void __iomem *reg, u32 bits)
+{
+ writel_relaxed(readl_relaxed(reg) | bits, reg);
+}
+
+static inline void stm32_usbphyc_clr_bits(void __iomem *reg, u32 bits)
+{
+ writel_relaxed(readl_relaxed(reg) & ~bits, reg);
+}
+
+static void stm32_usbphyc_get_pll_params(u32 clk_rate,
+ struct pll_params *pll_params)
+{
+ unsigned long long fvco, ndiv, frac;
+
+ /* _
+ * | FVCO = INFF*2*(NDIV + FRACT/2^16) when DITHER_DISABLE[1] = 1
+ * | FVCO = 2880MHz
+ * <
+ * | NDIV = integer part of input bits to set the LDF
+ * |_FRACT = fractional part of input bits to set the LDF
+ * => PLLNDIV = integer part of (FVCO / (INFF*2))
+ * => PLLFRACIN = fractional part of(FVCO / INFF*2) * 2^16
+ * <=> PLLFRACIN = ((FVCO / (INFF*2)) - PLLNDIV) * 2^16
+ */
+ fvco = (unsigned long long)PLL_FVCO_MHZ * HZ_PER_MHZ;
+
+ ndiv = fvco;
+ do_div(ndiv, (clk_rate * 2));
+ pll_params->ndiv = (u8)ndiv;
+
+ frac = fvco * (1 << 16);
+ do_div(frac, (clk_rate * 2));
+ frac = frac - (ndiv * (1 << 16));
+ pll_params->frac = (u16)frac;
+}
+
+static int stm32_usbphyc_pll_init(struct stm32_usbphyc *usbphyc)
+{
+ struct pll_params pll_params;
+ u32 clk_rate = clk_get_rate(usbphyc->clk);
+ u32 ndiv, frac;
+ u32 usbphyc_pll;
+
+ if ((clk_rate < PLL_INFF_MIN_RATE_HZ) ||
+ (clk_rate > PLL_INFF_MAX_RATE_HZ)) {
+ dev_err(usbphyc->dev, "input clk freq (%dHz) out of range\n",
+ clk_rate);
+ return -EINVAL;
+ }
+
+ stm32_usbphyc_get_pll_params(clk_rate, &pll_params);
+ ndiv = FIELD_PREP(PLLNDIV, pll_params.ndiv);
+ frac = FIELD_PREP(PLLFRACIN, pll_params.frac);
+
+ usbphyc_pll = PLLDITHEN1 | PLLDITHEN0 | PLLSTRBYP | ndiv;
+
+ if (pll_params.frac)
+ usbphyc_pll |= PLLFRACCTL | frac;
+
+ writel_relaxed(usbphyc_pll, usbphyc->base + STM32_USBPHYC_PLL);
+
+ dev_dbg(usbphyc->dev, "input clk freq=%dHz, ndiv=%lu, frac=%lu\n",
+ clk_rate, FIELD_GET(PLLNDIV, usbphyc_pll),
+ FIELD_GET(PLLFRACIN, usbphyc_pll));
+
+ return 0;
+}
+
+static bool stm32_usbphyc_has_one_phy_active(struct stm32_usbphyc *usbphyc)
+{
+ int i;
+
+ for (i = 0; i < usbphyc->nphys; i++)
+ if (usbphyc->phys[i]->active)
+ return true;
+
+ return false;
+}
+
+static int stm32_usbphyc_pll_enable(struct stm32_usbphyc *usbphyc)
+{
+ void __iomem *pll_reg = usbphyc->base + STM32_USBPHYC_PLL;
+ bool pllen = (readl_relaxed(pll_reg) & PLLEN);
+ int ret;
+
+ /* Check if one phy port has already configured the pll */
+ if (pllen && stm32_usbphyc_has_one_phy_active(usbphyc))
+ return 0;
+
+ if (pllen) {
+ stm32_usbphyc_clr_bits(pll_reg, PLLEN);
+ /* Wait for minimum width of powerdown pulse (ENABLE = Low) */
+ udelay(PLL_PWR_DOWN_TIME_US);
+ }
+
+ ret = stm32_usbphyc_pll_init(usbphyc);
+ if (ret)
+ return ret;
+
+ stm32_usbphyc_set_bits(pll_reg, PLLEN);
+
+ /* Wait for maximum lock time */
+ udelay(PLL_LOCK_TIME_US);
+
+ if (!(readl_relaxed(pll_reg) & PLLEN)) {
+ dev_err(usbphyc->dev, "PLLEN not set\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int stm32_usbphyc_pll_disable(struct stm32_usbphyc *usbphyc)
+{
+ void __iomem *pll_reg = usbphyc->base + STM32_USBPHYC_PLL;
+
+ /* Check if other phy port active */
+ if (stm32_usbphyc_has_one_phy_active(usbphyc))
+ return 0;
+
+ stm32_usbphyc_clr_bits(pll_reg, PLLEN);
+ /* Wait for minimum width of powerdown pulse (ENABLE = Low) */
+ udelay(PLL_PWR_DOWN_TIME_US);
+
+ if (readl_relaxed(pll_reg) & PLLEN) {
+ dev_err(usbphyc->dev, "PLL not reset\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int stm32_usbphyc_phy_init(struct phy *phy)
+{
+ struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy);
+ struct stm32_usbphyc *usbphyc = usbphyc_phy->usbphyc;
+ int ret;
+
+ ret = stm32_usbphyc_pll_enable(usbphyc);
+ if (ret)
+ return ret;
+
+ usbphyc_phy->active = true;
+
+ return 0;
+}
+
+static int stm32_usbphyc_phy_exit(struct phy *phy)
+{
+ struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy);
+ struct stm32_usbphyc *usbphyc = usbphyc_phy->usbphyc;
+
+ usbphyc_phy->active = false;
+
+ return stm32_usbphyc_pll_disable(usbphyc);
+}
+
+static int stm32_usbphyc_phy_power_on(struct phy *phy)
+{
+ struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy);
+
+ return regulator_bulk_enable(NUM_SUPPLIES, usbphyc_phy->supplies);
+}
+
+static int stm32_usbphyc_phy_power_off(struct phy *phy)
+{
+ struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy);
+
+ return regulator_bulk_disable(NUM_SUPPLIES, usbphyc_phy->supplies);
+}
+
+static const struct phy_ops stm32_usbphyc_phy_ops = {
+ .init = stm32_usbphyc_phy_init,
+ .exit = stm32_usbphyc_phy_exit,
+ .power_on = stm32_usbphyc_phy_power_on,
+ .power_off = stm32_usbphyc_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static void stm32_usbphyc_switch_setup(struct stm32_usbphyc *usbphyc,
+ u32 utmi_switch)
+{
+ if (!utmi_switch)
+ stm32_usbphyc_clr_bits(usbphyc->base + STM32_USBPHYC_MISC,
+ SWITHOST);
+ else
+ stm32_usbphyc_set_bits(usbphyc->base + STM32_USBPHYC_MISC,
+ SWITHOST);
+ usbphyc->switch_setup = utmi_switch;
+}
+
+static struct phy *stm32_usbphyc_of_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct stm32_usbphyc *usbphyc = dev_get_drvdata(dev);
+ struct stm32_usbphyc_phy *usbphyc_phy = NULL;
+ struct device_node *phynode = args->np;
+ int port = 0;
+
+ for (port = 0; port < usbphyc->nphys; port++) {
+ if (phynode == usbphyc->phys[port]->phy->dev.of_node) {
+ usbphyc_phy = usbphyc->phys[port];
+ break;
+ }
+ }
+ if (!usbphyc_phy) {
+ dev_err(dev, "failed to find phy\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (((usbphyc_phy->index == 0) && (args->args_count != 0)) ||
+ ((usbphyc_phy->index == 1) && (args->args_count != 1))) {
+ dev_err(dev, "invalid number of cells for phy port%d\n",
+ usbphyc_phy->index);
+ return ERR_PTR(-EINVAL);
+ }
+
+ /* Configure the UTMI switch for PHY port#2 */
+ if (usbphyc_phy->index == 1) {
+ if (usbphyc->switch_setup < 0) {
+ stm32_usbphyc_switch_setup(usbphyc, args->args[0]);
+ } else {
+ if (args->args[0] != usbphyc->switch_setup) {
+ dev_err(dev, "phy port1 already used\n");
+ return ERR_PTR(-EBUSY);
+ }
+ }
+ }
+
+ return usbphyc_phy->phy;
+}
+
+static int stm32_usbphyc_probe(struct platform_device *pdev)
+{
+ struct stm32_usbphyc *usbphyc;
+ struct device *dev = &pdev->dev;
+ struct device_node *child, *np = dev->of_node;
+ struct resource *res;
+ struct phy_provider *phy_provider;
+ u32 version;
+ int ret, port = 0;
+
+ usbphyc = devm_kzalloc(dev, sizeof(*usbphyc), GFP_KERNEL);
+ if (!usbphyc)
+ return -ENOMEM;
+ usbphyc->dev = dev;
+ dev_set_drvdata(dev, usbphyc);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ usbphyc->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(usbphyc->base))
+ return PTR_ERR(usbphyc->base);
+
+ usbphyc->clk = devm_clk_get(dev, 0);
+ if (IS_ERR(usbphyc->clk)) {
+ ret = PTR_ERR(usbphyc->clk);
+ dev_err(dev, "clk get failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(usbphyc->clk);
+ if (ret) {
+ dev_err(dev, "clk enable failed: %d\n", ret);
+ return ret;
+ }
+
+ usbphyc->rst = devm_reset_control_get(dev, 0);
+ if (!IS_ERR(usbphyc->rst)) {
+ reset_control_assert(usbphyc->rst);
+ udelay(2);
+ reset_control_deassert(usbphyc->rst);
+ }
+
+ usbphyc->switch_setup = -EINVAL;
+ usbphyc->nphys = of_get_child_count(np);
+ usbphyc->phys = devm_kcalloc(dev, usbphyc->nphys,
+ sizeof(*usbphyc->phys), GFP_KERNEL);
+ if (!usbphyc->phys) {
+ ret = -ENOMEM;
+ goto clk_disable;
+ }
+
+ for_each_child_of_node(np, child) {
+ struct stm32_usbphyc_phy *usbphyc_phy;
+ struct phy *phy;
+ u32 index;
+ int i;
+
+ phy = devm_phy_create(dev, child, &stm32_usbphyc_phy_ops);
+ if (IS_ERR(phy)) {
+ ret = PTR_ERR(phy);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to create phy%d: %d\n",
+ port, ret);
+ goto put_child;
+ }
+
+ usbphyc_phy = devm_kzalloc(dev, sizeof(*usbphyc_phy),
+ GFP_KERNEL);
+ if (!usbphyc_phy) {
+ ret = -ENOMEM;
+ goto put_child;
+ }
+
+ for (i = 0; i < NUM_SUPPLIES; i++)
+ usbphyc_phy->supplies[i].supply = supplies_names[i];
+
+ ret = devm_regulator_bulk_get(&phy->dev, NUM_SUPPLIES,
+ usbphyc_phy->supplies);
+ if (ret) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(&phy->dev,
+ "failed to get regulators: %d\n", ret);
+ goto put_child;
+ }
+
+ ret = of_property_read_u32(child, "reg", &index);
+ if (ret || index > usbphyc->nphys) {
+ dev_err(&phy->dev, "invalid reg property: %d\n", ret);
+ goto put_child;
+ }
+
+ usbphyc->phys[port] = usbphyc_phy;
+ phy_set_bus_width(phy, 8);
+ phy_set_drvdata(phy, usbphyc_phy);
+
+ usbphyc->phys[port]->phy = phy;
+ usbphyc->phys[port]->usbphyc = usbphyc;
+ usbphyc->phys[port]->index = index;
+ usbphyc->phys[port]->active = false;
+
+ port++;
+ }
+
+ phy_provider = devm_of_phy_provider_register(dev,
+ stm32_usbphyc_of_xlate);
+ if (IS_ERR(phy_provider)) {
+ ret = PTR_ERR(phy_provider);
+ dev_err(dev, "failed to register phy provider: %d\n", ret);
+ goto clk_disable;
+ }
+
+ version = readl_relaxed(usbphyc->base + STM32_USBPHYC_VERSION);
+ dev_info(dev, "registered rev:%lu.%lu\n",
+ FIELD_GET(MAJREV, version), FIELD_GET(MINREV, version));
+
+ return 0;
+
+put_child:
+ of_node_put(child);
+clk_disable:
+ clk_disable_unprepare(usbphyc->clk);
+
+ return ret;
+}
+
+static int stm32_usbphyc_remove(struct platform_device *pdev)
+{
+ struct stm32_usbphyc *usbphyc = dev_get_drvdata(&pdev->dev);
+
+ clk_disable_unprepare(usbphyc->clk);
+
+ return 0;
+}
+
+static const struct of_device_id stm32_usbphyc_of_match[] = {
+ { .compatible = "st,stm32mp1-usbphyc", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, stm32_usbphyc_of_match);
+
+static struct platform_driver stm32_usbphyc_driver = {
+ .probe = stm32_usbphyc_probe,
+ .remove = stm32_usbphyc_remove,
+ .driver = {
+ .of_match_table = stm32_usbphyc_of_match,
+ .name = "stm32-usbphyc",
+ }
+};
+module_platform_driver(stm32_usbphyc_driver);
+
+MODULE_DESCRIPTION("STMicroelectronics STM32 USBPHYC driver");
+MODULE_AUTHOR("Amelie Delaunay <amelie.delaunay@st.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/tegra/Kconfig b/drivers/phy/tegra/Kconfig
new file mode 100644
index 000000000..a3b1de953
--- /dev/null
+++ b/drivers/phy/tegra/Kconfig
@@ -0,0 +1,8 @@
+config PHY_TEGRA_XUSB
+ tristate "NVIDIA Tegra XUSB pad controller driver"
+ depends on ARCH_TEGRA
+ help
+ Choose this option if you have an NVIDIA Tegra SoC.
+
+ To compile this driver as a module, choose M here: the module will
+ be called phy-tegra-xusb.
diff --git a/drivers/phy/tegra/Makefile b/drivers/phy/tegra/Makefile
new file mode 100644
index 000000000..898589238
--- /dev/null
+++ b/drivers/phy/tegra/Makefile
@@ -0,0 +1,6 @@
+obj-$(CONFIG_PHY_TEGRA_XUSB) += phy-tegra-xusb.o
+
+phy-tegra-xusb-y += xusb.o
+phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_124_SOC) += xusb-tegra124.o
+phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_132_SOC) += xusb-tegra124.o
+phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_210_SOC) += xusb-tegra210.o
diff --git a/drivers/phy/tegra/xusb-tegra124.c b/drivers/phy/tegra/xusb-tegra124.c
new file mode 100644
index 000000000..c45cbedc6
--- /dev/null
+++ b/drivers/phy/tegra/xusb-tegra124.c
@@ -0,0 +1,1751 @@
+/*
+ * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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/delay.h>
+#include <linux/io.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+
+#include <soc/tegra/fuse.h>
+
+#include "xusb.h"
+
+#define FUSE_SKU_CALIB_HS_CURR_LEVEL_PADX_SHIFT(x) ((x) ? 15 : 0)
+#define FUSE_SKU_CALIB_HS_CURR_LEVEL_PAD_MASK 0x3f
+#define FUSE_SKU_CALIB_HS_IREF_CAP_SHIFT 13
+#define FUSE_SKU_CALIB_HS_IREF_CAP_MASK 0x3
+#define FUSE_SKU_CALIB_HS_SQUELCH_LEVEL_SHIFT 11
+#define FUSE_SKU_CALIB_HS_SQUELCH_LEVEL_MASK 0x3
+#define FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_SHIFT 7
+#define FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_MASK 0xf
+
+#define XUSB_PADCTL_USB2_PORT_CAP 0x008
+#define XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_SHIFT(x) ((x) * 4)
+#define XUSB_PADCTL_USB2_PORT_CAP_PORT_CAP_MASK 0x3
+#define XUSB_PADCTL_USB2_PORT_CAP_DISABLED 0x0
+#define XUSB_PADCTL_USB2_PORT_CAP_HOST 0x1
+#define XUSB_PADCTL_USB2_PORT_CAP_DEVICE 0x2
+#define XUSB_PADCTL_USB2_PORT_CAP_OTG 0x3
+
+#define XUSB_PADCTL_SS_PORT_MAP 0x014
+#define XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(x) (1 << (((x) * 4) + 3))
+#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_SHIFT(x) ((x) * 4)
+#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(x) (0x7 << ((x) * 4))
+#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(x, v) (((v) & 0x7) << ((x) * 4))
+#define XUSB_PADCTL_SS_PORT_MAP_PORT_MAP_MASK 0x7
+
+#define XUSB_PADCTL_ELPG_PROGRAM 0x01c
+#define XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN (1 << 26)
+#define XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY (1 << 25)
+#define XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN (1 << 24)
+#define XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_VCORE_DOWN(x) (1 << (18 + (x) * 4))
+#define XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN_EARLY(x) \
+ (1 << (17 + (x) * 4))
+#define XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN(x) (1 << (16 + (x) * 4))
+
+#define XUSB_PADCTL_IOPHY_PLL_P0_CTL1 0x040
+#define XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL0_LOCKDET (1 << 19)
+#define XUSB_PADCTL_IOPHY_PLL_P0_CTL1_REFCLK_SEL_MASK (0xf << 12)
+#define XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL_RST (1 << 1)
+
+#define XUSB_PADCTL_IOPHY_PLL_P0_CTL2 0x044
+#define XUSB_PADCTL_IOPHY_PLL_P0_CTL2_REFCLKBUF_EN (1 << 6)
+#define XUSB_PADCTL_IOPHY_PLL_P0_CTL2_TXCLKREF_EN (1 << 5)
+#define XUSB_PADCTL_IOPHY_PLL_P0_CTL2_TXCLKREF_SEL (1 << 4)
+
+#define XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(x) (0x058 + (x) * 4)
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_SHIFT 24
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_MASK 0xff
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_VAL 0x24
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT 16
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_MASK 0x3f
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT 8
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_MASK 0x3f
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_SHIFT 8
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_MASK 0xffff
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_VAL 0xf070
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_SHIFT 4
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_MASK 0xf
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_VAL 0xf
+
+#define XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(x) (0x068 + (x) * 4)
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT 24
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_MASK 0x1f
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT 16
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_MASK 0x7f
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_VAL 0x002008ee
+
+#define XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL2(x) ((x) < 2 ? 0x078 + (x) * 4 : \
+ 0x0f8 + (x) * 4)
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_SHIFT 28
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_MASK 0x3
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_VAL 0x1
+
+#define XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL5(x) ((x) < 2 ? 0x090 + (x) * 4 : \
+ 0x11c + (x) * 4)
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL5_RX_QEYE_EN (1 << 8)
+
+#define XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL6(x) ((x) < 2 ? 0x098 + (x) * 4 : \
+ 0x128 + (x) * 4)
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT 24
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_G_Z_MASK 0x3f
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_TAP_MASK 0x1f
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_AMP_MASK 0x7f
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT 16
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK 0xff
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_G_Z 0x21
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_TAP 0x32
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_AMP 0x33
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_CTLE_Z 0x48
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_LATCH_G_Z 0xa1
+
+#define XUSB_PADCTL_USB2_OTG_PADX_CTL0(x) (0x0a0 + (x) * 4)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD_ZI (1 << 21)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD2 (1 << 20)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD (1 << 19)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_SHIFT 14
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_MASK 0x3
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_VAL(x) ((x) ? 0x0 : 0x3)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_SHIFT 6
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_MASK 0x3f
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_VAL 0x0e
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT 0
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_MASK 0x3f
+
+#define XUSB_PADCTL_USB2_OTG_PADX_CTL1(x) (0x0ac + (x) * 4)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_SHIFT 9
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_MASK 0x3
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT 3
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_MASK 0x7
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR (1 << 2)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DISC_FORCE_POWERUP (1 << 1)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_CHRP_FORCE_POWERUP (1 << 0)
+
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0 0x0b8
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD (1 << 12)
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT 2
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_MASK 0x7
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_VAL 0x5
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT 0
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_MASK 0x3
+
+#define XUSB_PADCTL_HSIC_PADX_CTL0(x) (0x0c0 + (x) * 4)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_SHIFT 12
+#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_MASK 0x7
+#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_SHIFT 8
+#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_MASK 0x7
+#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_SHIFT 4
+#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_MASK 0x7
+#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_SHIFT 0
+#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_MASK 0x7
+
+#define XUSB_PADCTL_HSIC_PADX_CTL1(x) (0x0c8 + (x) * 4)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_RPU_STROBE (1 << 10)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_RPU_DATA (1 << 9)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_RPD_STROBE (1 << 8)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_RPD_DATA (1 << 7)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_PD_ZI (1 << 5)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_PD_RX (1 << 4)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_PD_TRX (1 << 3)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_PD_TX (1 << 2)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_AUTO_TERM_EN (1 << 0)
+
+#define XUSB_PADCTL_HSIC_PADX_CTL2(x) (0x0d0 + (x) * 4)
+#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT 4
+#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK 0x7
+#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT 0
+#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK 0x7
+
+#define XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL 0x0e0
+#define XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL_STRB_TRIM_MASK 0x1f
+
+#define XUSB_PADCTL_USB3_PAD_MUX 0x134
+#define XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(x) (1 << (1 + (x)))
+#define XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(x) (1 << (6 + (x)))
+
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1 0x138
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_LOCKDET (1 << 27)
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_MODE (1 << 24)
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_SHIFT 20
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_MASK 0x3
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD (1 << 3)
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_RST (1 << 1)
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_IDDQ (1 << 0)
+
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2 0x13c
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_SHIFT 20
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_MASK 0xf
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_SHIFT 16
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_MASK 0xf
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_TCLKOUT_EN (1 << 12)
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_TXCLKREF_SEL (1 << 4)
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_SHIFT 0
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_MASK 0x7
+
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL3 0x140
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL3_RCAL_BYPASS (1 << 7)
+
+#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1 0x148
+#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD (1 << 1)
+#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ (1 << 0)
+
+#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL2 0x14c
+
+#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL5 0x158
+
+#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL6 0x15c
+
+struct tegra124_xusb_fuse_calibration {
+ u32 hs_curr_level[3];
+ u32 hs_iref_cap;
+ u32 hs_term_range_adj;
+ u32 hs_squelch_level;
+};
+
+struct tegra124_xusb_padctl {
+ struct tegra_xusb_padctl base;
+
+ struct tegra124_xusb_fuse_calibration fuse;
+};
+
+static inline struct tegra124_xusb_padctl *
+to_tegra124_xusb_padctl(struct tegra_xusb_padctl *padctl)
+{
+ return container_of(padctl, struct tegra124_xusb_padctl, base);
+}
+
+static int tegra124_xusb_padctl_enable(struct tegra_xusb_padctl *padctl)
+{
+ u32 value;
+
+ mutex_lock(&padctl->lock);
+
+ if (padctl->enable++ > 0)
+ goto out;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value &= ~XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN;
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value &= ~XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY;
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value &= ~XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN;
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+out:
+ mutex_unlock(&padctl->lock);
+ return 0;
+}
+
+static int tegra124_xusb_padctl_disable(struct tegra_xusb_padctl *padctl)
+{
+ u32 value;
+
+ mutex_lock(&padctl->lock);
+
+ if (WARN_ON(padctl->enable == 0))
+ goto out;
+
+ if (--padctl->enable > 0)
+ goto out;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value |= XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN;
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value |= XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY;
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value |= XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN;
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+out:
+ mutex_unlock(&padctl->lock);
+ return 0;
+}
+
+static int tegra124_usb3_save_context(struct tegra_xusb_padctl *padctl,
+ unsigned int index)
+{
+ struct tegra_xusb_usb3_port *port;
+ struct tegra_xusb_lane *lane;
+ u32 value, offset;
+
+ port = tegra_xusb_find_usb3_port(padctl, index);
+ if (!port)
+ return -ENODEV;
+
+ port->context_saved = true;
+ lane = port->base.lane;
+
+ if (lane->pad == padctl->pcie)
+ offset = XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL6(lane->index);
+ else
+ offset = XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL6;
+
+ value = padctl_readl(padctl, offset);
+ value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK <<
+ XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT);
+ value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_TAP <<
+ XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT;
+ padctl_writel(padctl, value, offset);
+
+ value = padctl_readl(padctl, offset) >>
+ XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT;
+ port->tap1 = value & XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_TAP_MASK;
+
+ value = padctl_readl(padctl, offset);
+ value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK <<
+ XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT);
+ value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_AMP <<
+ XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT;
+ padctl_writel(padctl, value, offset);
+
+ value = padctl_readl(padctl, offset) >>
+ XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT;
+ port->amp = value & XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_AMP_MASK;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(index));
+ value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_MASK <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT) |
+ (XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_MASK <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT));
+ value |= (port->tap1 <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT) |
+ (port->amp <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT);
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(index));
+
+ value = padctl_readl(padctl, offset);
+ value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK <<
+ XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT);
+ value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_LATCH_G_Z <<
+ XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT;
+ padctl_writel(padctl, value, offset);
+
+ value = padctl_readl(padctl, offset);
+ value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK <<
+ XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT);
+ value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_G_Z <<
+ XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT;
+ padctl_writel(padctl, value, offset);
+
+ value = padctl_readl(padctl, offset) >>
+ XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT;
+ port->ctle_g = value &
+ XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_G_Z_MASK;
+
+ value = padctl_readl(padctl, offset);
+ value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK <<
+ XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT);
+ value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_CTLE_Z <<
+ XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT;
+ padctl_writel(padctl, value, offset);
+
+ value = padctl_readl(padctl, offset) >>
+ XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT;
+ port->ctle_z = value &
+ XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_G_Z_MASK;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(index));
+ value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_MASK <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT) |
+ (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_MASK <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT));
+ value |= (port->ctle_g <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT) |
+ (port->ctle_z <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT);
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(index));
+
+ return 0;
+}
+
+static int tegra124_hsic_set_idle(struct tegra_xusb_padctl *padctl,
+ unsigned int index, bool idle)
+{
+ u32 value;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+
+ if (idle)
+ value |= XUSB_PADCTL_HSIC_PAD_CTL1_RPD_DATA |
+ XUSB_PADCTL_HSIC_PAD_CTL1_RPU_STROBE;
+ else
+ value &= ~(XUSB_PADCTL_HSIC_PAD_CTL1_RPD_DATA |
+ XUSB_PADCTL_HSIC_PAD_CTL1_RPU_STROBE);
+
+ padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+
+ return 0;
+}
+
+#define TEGRA124_LANE(_name, _offset, _shift, _mask, _type) \
+ { \
+ .name = _name, \
+ .offset = _offset, \
+ .shift = _shift, \
+ .mask = _mask, \
+ .num_funcs = ARRAY_SIZE(tegra124_##_type##_functions), \
+ .funcs = tegra124_##_type##_functions, \
+ }
+
+static const char * const tegra124_usb2_functions[] = {
+ "snps",
+ "xusb",
+ "uart",
+};
+
+static const struct tegra_xusb_lane_soc tegra124_usb2_lanes[] = {
+ TEGRA124_LANE("usb2-0", 0x004, 0, 0x3, usb2),
+ TEGRA124_LANE("usb2-1", 0x004, 2, 0x3, usb2),
+ TEGRA124_LANE("usb2-2", 0x004, 4, 0x3, usb2),
+};
+
+static struct tegra_xusb_lane *
+tegra124_usb2_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+ unsigned int index)
+{
+ struct tegra_xusb_usb2_lane *usb2;
+ int err;
+
+ usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL);
+ if (!usb2)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&usb2->base.list);
+ usb2->base.soc = &pad->soc->lanes[index];
+ usb2->base.index = index;
+ usb2->base.pad = pad;
+ usb2->base.np = np;
+
+ err = tegra_xusb_lane_parse_dt(&usb2->base, np);
+ if (err < 0) {
+ kfree(usb2);
+ return ERR_PTR(err);
+ }
+
+ return &usb2->base;
+}
+
+static void tegra124_usb2_lane_remove(struct tegra_xusb_lane *lane)
+{
+ struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane);
+
+ kfree(usb2);
+}
+
+static const struct tegra_xusb_lane_ops tegra124_usb2_lane_ops = {
+ .probe = tegra124_usb2_lane_probe,
+ .remove = tegra124_usb2_lane_remove,
+};
+
+static int tegra124_usb2_phy_init(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+ return tegra124_xusb_padctl_enable(lane->pad->padctl);
+}
+
+static int tegra124_usb2_phy_exit(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+ return tegra124_xusb_padctl_disable(lane->pad->padctl);
+}
+
+static int tegra124_usb2_phy_power_on(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane);
+ struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ struct tegra124_xusb_padctl *priv;
+ struct tegra_xusb_usb2_port *port;
+ unsigned int index = lane->index;
+ u32 value;
+ int err;
+
+ port = tegra_xusb_find_usb2_port(padctl, index);
+ if (!port) {
+ dev_err(&phy->dev, "no port found for USB2 lane %u\n", index);
+ return -ENODEV;
+ }
+
+ priv = to_tegra124_xusb_padctl(padctl);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+ value &= ~((XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_MASK <<
+ XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT) |
+ (XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_MASK <<
+ XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT));
+ value |= (priv->fuse.hs_squelch_level <<
+ XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT) |
+ (XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_VAL <<
+ XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT);
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_PORT_CAP);
+ value &= ~(XUSB_PADCTL_USB2_PORT_CAP_PORT_CAP_MASK <<
+ XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_SHIFT(index));
+ value |= XUSB_PADCTL_USB2_PORT_CAP_HOST <<
+ XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_SHIFT(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_PORT_CAP);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
+ value &= ~((XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_MASK <<
+ XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT) |
+ (XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_MASK <<
+ XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_SHIFT) |
+ (XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_MASK <<
+ XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_SHIFT) |
+ XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD |
+ XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD2 |
+ XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD_ZI);
+ value |= (priv->fuse.hs_curr_level[index] +
+ usb2->hs_curr_level_offset) <<
+ XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT;
+ value |= XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_VAL <<
+ XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_SHIFT;
+ value |= XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_VAL(index) <<
+ XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_SHIFT;
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
+ value &= ~((XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_MASK <<
+ XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT) |
+ (XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_MASK <<
+ XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_SHIFT) |
+ XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR |
+ XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_CHRP_FORCE_POWERUP |
+ XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DISC_FORCE_POWERUP);
+ value |= (priv->fuse.hs_term_range_adj <<
+ XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT) |
+ (priv->fuse.hs_iref_cap <<
+ XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_SHIFT);
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
+
+ err = regulator_enable(port->supply);
+ if (err)
+ return err;
+
+ mutex_lock(&pad->lock);
+
+ if (pad->enable++ > 0)
+ goto out;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+ value &= ~XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD;
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+
+out:
+ mutex_unlock(&pad->lock);
+ return 0;
+}
+
+static int tegra124_usb2_phy_power_off(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ struct tegra_xusb_usb2_port *port;
+ u32 value;
+
+ port = tegra_xusb_find_usb2_port(padctl, lane->index);
+ if (!port) {
+ dev_err(&phy->dev, "no port found for USB2 lane %u\n",
+ lane->index);
+ return -ENODEV;
+ }
+
+ mutex_lock(&pad->lock);
+
+ if (WARN_ON(pad->enable == 0))
+ goto out;
+
+ if (--pad->enable > 0)
+ goto out;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+ value |= XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD;
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+
+out:
+ regulator_disable(port->supply);
+ mutex_unlock(&pad->lock);
+ return 0;
+}
+
+static const struct phy_ops tegra124_usb2_phy_ops = {
+ .init = tegra124_usb2_phy_init,
+ .exit = tegra124_usb2_phy_exit,
+ .power_on = tegra124_usb2_phy_power_on,
+ .power_off = tegra124_usb2_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra124_usb2_pad_probe(struct tegra_xusb_padctl *padctl,
+ const struct tegra_xusb_pad_soc *soc,
+ struct device_node *np)
+{
+ struct tegra_xusb_usb2_pad *usb2;
+ struct tegra_xusb_pad *pad;
+ int err;
+
+ usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL);
+ if (!usb2)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_init(&usb2->lock);
+
+ pad = &usb2->base;
+ pad->ops = &tegra124_usb2_lane_ops;
+ pad->soc = soc;
+
+ err = tegra_xusb_pad_init(pad, padctl, np);
+ if (err < 0) {
+ kfree(usb2);
+ goto out;
+ }
+
+ err = tegra_xusb_pad_register(pad, &tegra124_usb2_phy_ops);
+ if (err < 0)
+ goto unregister;
+
+ dev_set_drvdata(&pad->dev, pad);
+
+ return pad;
+
+unregister:
+ device_unregister(&pad->dev);
+out:
+ return ERR_PTR(err);
+}
+
+static void tegra124_usb2_pad_remove(struct tegra_xusb_pad *pad)
+{
+ struct tegra_xusb_usb2_pad *usb2 = to_usb2_pad(pad);
+
+ kfree(usb2);
+}
+
+static const struct tegra_xusb_pad_ops tegra124_usb2_ops = {
+ .probe = tegra124_usb2_pad_probe,
+ .remove = tegra124_usb2_pad_remove,
+};
+
+static const struct tegra_xusb_pad_soc tegra124_usb2_pad = {
+ .name = "usb2",
+ .num_lanes = ARRAY_SIZE(tegra124_usb2_lanes),
+ .lanes = tegra124_usb2_lanes,
+ .ops = &tegra124_usb2_ops,
+};
+
+static const char * const tegra124_ulpi_functions[] = {
+ "snps",
+ "xusb",
+};
+
+static const struct tegra_xusb_lane_soc tegra124_ulpi_lanes[] = {
+ TEGRA124_LANE("ulpi-0", 0x004, 12, 0x1, ulpi),
+};
+
+static struct tegra_xusb_lane *
+tegra124_ulpi_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+ unsigned int index)
+{
+ struct tegra_xusb_ulpi_lane *ulpi;
+ int err;
+
+ ulpi = kzalloc(sizeof(*ulpi), GFP_KERNEL);
+ if (!ulpi)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&ulpi->base.list);
+ ulpi->base.soc = &pad->soc->lanes[index];
+ ulpi->base.index = index;
+ ulpi->base.pad = pad;
+ ulpi->base.np = np;
+
+ err = tegra_xusb_lane_parse_dt(&ulpi->base, np);
+ if (err < 0) {
+ kfree(ulpi);
+ return ERR_PTR(err);
+ }
+
+ return &ulpi->base;
+}
+
+static void tegra124_ulpi_lane_remove(struct tegra_xusb_lane *lane)
+{
+ struct tegra_xusb_ulpi_lane *ulpi = to_ulpi_lane(lane);
+
+ kfree(ulpi);
+}
+
+static const struct tegra_xusb_lane_ops tegra124_ulpi_lane_ops = {
+ .probe = tegra124_ulpi_lane_probe,
+ .remove = tegra124_ulpi_lane_remove,
+};
+
+static int tegra124_ulpi_phy_init(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+ return tegra124_xusb_padctl_enable(lane->pad->padctl);
+}
+
+static int tegra124_ulpi_phy_exit(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+ return tegra124_xusb_padctl_disable(lane->pad->padctl);
+}
+
+static int tegra124_ulpi_phy_power_on(struct phy *phy)
+{
+ return 0;
+}
+
+static int tegra124_ulpi_phy_power_off(struct phy *phy)
+{
+ return 0;
+}
+
+static const struct phy_ops tegra124_ulpi_phy_ops = {
+ .init = tegra124_ulpi_phy_init,
+ .exit = tegra124_ulpi_phy_exit,
+ .power_on = tegra124_ulpi_phy_power_on,
+ .power_off = tegra124_ulpi_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra124_ulpi_pad_probe(struct tegra_xusb_padctl *padctl,
+ const struct tegra_xusb_pad_soc *soc,
+ struct device_node *np)
+{
+ struct tegra_xusb_ulpi_pad *ulpi;
+ struct tegra_xusb_pad *pad;
+ int err;
+
+ ulpi = kzalloc(sizeof(*ulpi), GFP_KERNEL);
+ if (!ulpi)
+ return ERR_PTR(-ENOMEM);
+
+ pad = &ulpi->base;
+ pad->ops = &tegra124_ulpi_lane_ops;
+ pad->soc = soc;
+
+ err = tegra_xusb_pad_init(pad, padctl, np);
+ if (err < 0) {
+ kfree(ulpi);
+ goto out;
+ }
+
+ err = tegra_xusb_pad_register(pad, &tegra124_ulpi_phy_ops);
+ if (err < 0)
+ goto unregister;
+
+ dev_set_drvdata(&pad->dev, pad);
+
+ return pad;
+
+unregister:
+ device_unregister(&pad->dev);
+out:
+ return ERR_PTR(err);
+}
+
+static void tegra124_ulpi_pad_remove(struct tegra_xusb_pad *pad)
+{
+ struct tegra_xusb_ulpi_pad *ulpi = to_ulpi_pad(pad);
+
+ kfree(ulpi);
+}
+
+static const struct tegra_xusb_pad_ops tegra124_ulpi_ops = {
+ .probe = tegra124_ulpi_pad_probe,
+ .remove = tegra124_ulpi_pad_remove,
+};
+
+static const struct tegra_xusb_pad_soc tegra124_ulpi_pad = {
+ .name = "ulpi",
+ .num_lanes = ARRAY_SIZE(tegra124_ulpi_lanes),
+ .lanes = tegra124_ulpi_lanes,
+ .ops = &tegra124_ulpi_ops,
+};
+
+static const char * const tegra124_hsic_functions[] = {
+ "snps",
+ "xusb",
+};
+
+static const struct tegra_xusb_lane_soc tegra124_hsic_lanes[] = {
+ TEGRA124_LANE("hsic-0", 0x004, 14, 0x1, hsic),
+ TEGRA124_LANE("hsic-1", 0x004, 15, 0x1, hsic),
+};
+
+static struct tegra_xusb_lane *
+tegra124_hsic_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+ unsigned int index)
+{
+ struct tegra_xusb_hsic_lane *hsic;
+ int err;
+
+ hsic = kzalloc(sizeof(*hsic), GFP_KERNEL);
+ if (!hsic)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&hsic->base.list);
+ hsic->base.soc = &pad->soc->lanes[index];
+ hsic->base.index = index;
+ hsic->base.pad = pad;
+ hsic->base.np = np;
+
+ err = tegra_xusb_lane_parse_dt(&hsic->base, np);
+ if (err < 0) {
+ kfree(hsic);
+ return ERR_PTR(err);
+ }
+
+ return &hsic->base;
+}
+
+static void tegra124_hsic_lane_remove(struct tegra_xusb_lane *lane)
+{
+ struct tegra_xusb_hsic_lane *hsic = to_hsic_lane(lane);
+
+ kfree(hsic);
+}
+
+static const struct tegra_xusb_lane_ops tegra124_hsic_lane_ops = {
+ .probe = tegra124_hsic_lane_probe,
+ .remove = tegra124_hsic_lane_remove,
+};
+
+static int tegra124_hsic_phy_init(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+ return tegra124_xusb_padctl_enable(lane->pad->padctl);
+}
+
+static int tegra124_hsic_phy_exit(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+ return tegra124_xusb_padctl_disable(lane->pad->padctl);
+}
+
+static int tegra124_hsic_phy_power_on(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_hsic_lane *hsic = to_hsic_lane(lane);
+ struct tegra_xusb_hsic_pad *pad = to_hsic_pad(lane->pad);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ unsigned int index = lane->index;
+ u32 value;
+ int err;
+
+ err = regulator_enable(pad->supply);
+ if (err)
+ return err;
+
+ padctl_writel(padctl, hsic->strobe_trim,
+ XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+
+ if (hsic->auto_term)
+ value |= XUSB_PADCTL_HSIC_PAD_CTL1_AUTO_TERM_EN;
+ else
+ value &= ~XUSB_PADCTL_HSIC_PAD_CTL1_AUTO_TERM_EN;
+
+ padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+
+ value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(index));
+ value &= ~((XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_MASK <<
+ XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_SHIFT) |
+ (XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_MASK <<
+ XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_SHIFT) |
+ (XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_MASK <<
+ XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_SHIFT) |
+ (XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_MASK <<
+ XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_SHIFT));
+ value |= (hsic->tx_rtune_n <<
+ XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_SHIFT) |
+ (hsic->tx_rtune_p <<
+ XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_SHIFT) |
+ (hsic->tx_rslew_n <<
+ XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_SHIFT) |
+ (hsic->tx_rslew_p <<
+ XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_SHIFT);
+ padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL0(index));
+
+ value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL2(index));
+ value &= ~((XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK <<
+ XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT) |
+ (XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK <<
+ XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT));
+ value |= (hsic->rx_strobe_trim <<
+ XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT) |
+ (hsic->rx_data_trim <<
+ XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT);
+ padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL2(index));
+
+ value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+ value &= ~(XUSB_PADCTL_HSIC_PAD_CTL1_RPD_STROBE |
+ XUSB_PADCTL_HSIC_PAD_CTL1_RPU_DATA |
+ XUSB_PADCTL_HSIC_PAD_CTL1_PD_RX |
+ XUSB_PADCTL_HSIC_PAD_CTL1_PD_ZI |
+ XUSB_PADCTL_HSIC_PAD_CTL1_PD_TRX |
+ XUSB_PADCTL_HSIC_PAD_CTL1_PD_TX);
+ value |= XUSB_PADCTL_HSIC_PAD_CTL1_RPD_DATA |
+ XUSB_PADCTL_HSIC_PAD_CTL1_RPU_STROBE;
+ padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+
+ return 0;
+}
+
+static int tegra124_hsic_phy_power_off(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_hsic_pad *pad = to_hsic_pad(lane->pad);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ unsigned int index = lane->index;
+ u32 value;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+ value |= XUSB_PADCTL_HSIC_PAD_CTL1_PD_RX |
+ XUSB_PADCTL_HSIC_PAD_CTL1_PD_ZI |
+ XUSB_PADCTL_HSIC_PAD_CTL1_PD_TRX |
+ XUSB_PADCTL_HSIC_PAD_CTL1_PD_TX;
+ padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+
+ regulator_disable(pad->supply);
+
+ return 0;
+}
+
+static const struct phy_ops tegra124_hsic_phy_ops = {
+ .init = tegra124_hsic_phy_init,
+ .exit = tegra124_hsic_phy_exit,
+ .power_on = tegra124_hsic_phy_power_on,
+ .power_off = tegra124_hsic_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra124_hsic_pad_probe(struct tegra_xusb_padctl *padctl,
+ const struct tegra_xusb_pad_soc *soc,
+ struct device_node *np)
+{
+ struct tegra_xusb_hsic_pad *hsic;
+ struct tegra_xusb_pad *pad;
+ int err;
+
+ hsic = kzalloc(sizeof(*hsic), GFP_KERNEL);
+ if (!hsic)
+ return ERR_PTR(-ENOMEM);
+
+ pad = &hsic->base;
+ pad->ops = &tegra124_hsic_lane_ops;
+ pad->soc = soc;
+
+ err = tegra_xusb_pad_init(pad, padctl, np);
+ if (err < 0) {
+ kfree(hsic);
+ goto out;
+ }
+
+ err = tegra_xusb_pad_register(pad, &tegra124_hsic_phy_ops);
+ if (err < 0)
+ goto unregister;
+
+ dev_set_drvdata(&pad->dev, pad);
+
+ return pad;
+
+unregister:
+ device_unregister(&pad->dev);
+out:
+ return ERR_PTR(err);
+}
+
+static void tegra124_hsic_pad_remove(struct tegra_xusb_pad *pad)
+{
+ struct tegra_xusb_hsic_pad *hsic = to_hsic_pad(pad);
+
+ kfree(hsic);
+}
+
+static const struct tegra_xusb_pad_ops tegra124_hsic_ops = {
+ .probe = tegra124_hsic_pad_probe,
+ .remove = tegra124_hsic_pad_remove,
+};
+
+static const struct tegra_xusb_pad_soc tegra124_hsic_pad = {
+ .name = "hsic",
+ .num_lanes = ARRAY_SIZE(tegra124_hsic_lanes),
+ .lanes = tegra124_hsic_lanes,
+ .ops = &tegra124_hsic_ops,
+};
+
+static const char * const tegra124_pcie_functions[] = {
+ "pcie",
+ "usb3-ss",
+ "sata",
+};
+
+static const struct tegra_xusb_lane_soc tegra124_pcie_lanes[] = {
+ TEGRA124_LANE("pcie-0", 0x134, 16, 0x3, pcie),
+ TEGRA124_LANE("pcie-1", 0x134, 18, 0x3, pcie),
+ TEGRA124_LANE("pcie-2", 0x134, 20, 0x3, pcie),
+ TEGRA124_LANE("pcie-3", 0x134, 22, 0x3, pcie),
+ TEGRA124_LANE("pcie-4", 0x134, 24, 0x3, pcie),
+};
+
+static struct tegra_xusb_lane *
+tegra124_pcie_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+ unsigned int index)
+{
+ struct tegra_xusb_pcie_lane *pcie;
+ int err;
+
+ pcie = kzalloc(sizeof(*pcie), GFP_KERNEL);
+ if (!pcie)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&pcie->base.list);
+ pcie->base.soc = &pad->soc->lanes[index];
+ pcie->base.index = index;
+ pcie->base.pad = pad;
+ pcie->base.np = np;
+
+ err = tegra_xusb_lane_parse_dt(&pcie->base, np);
+ if (err < 0) {
+ kfree(pcie);
+ return ERR_PTR(err);
+ }
+
+ return &pcie->base;
+}
+
+static void tegra124_pcie_lane_remove(struct tegra_xusb_lane *lane)
+{
+ struct tegra_xusb_pcie_lane *pcie = to_pcie_lane(lane);
+
+ kfree(pcie);
+}
+
+static const struct tegra_xusb_lane_ops tegra124_pcie_lane_ops = {
+ .probe = tegra124_pcie_lane_probe,
+ .remove = tegra124_pcie_lane_remove,
+};
+
+static int tegra124_pcie_phy_init(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+ return tegra124_xusb_padctl_enable(lane->pad->padctl);
+}
+
+static int tegra124_pcie_phy_exit(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+ return tegra124_xusb_padctl_disable(lane->pad->padctl);
+}
+
+static int tegra124_pcie_phy_power_on(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ unsigned long timeout;
+ int err = -ETIMEDOUT;
+ u32 value;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL1);
+ value &= ~XUSB_PADCTL_IOPHY_PLL_P0_CTL1_REFCLK_SEL_MASK;
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_P0_CTL1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL2);
+ value |= XUSB_PADCTL_IOPHY_PLL_P0_CTL2_REFCLKBUF_EN |
+ XUSB_PADCTL_IOPHY_PLL_P0_CTL2_TXCLKREF_EN |
+ XUSB_PADCTL_IOPHY_PLL_P0_CTL2_TXCLKREF_SEL;
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_P0_CTL2);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL1);
+ value |= XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL_RST;
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_P0_CTL1);
+
+ timeout = jiffies + msecs_to_jiffies(50);
+
+ while (time_before(jiffies, timeout)) {
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL1);
+ if (value & XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL0_LOCKDET) {
+ err = 0;
+ break;
+ }
+
+ usleep_range(100, 200);
+ }
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX);
+ value |= XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->index);
+ padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX);
+
+ return err;
+}
+
+static int tegra124_pcie_phy_power_off(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ u32 value;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX);
+ value &= ~XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->index);
+ padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL1);
+ value &= ~XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL_RST;
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_P0_CTL1);
+
+ return 0;
+}
+
+static const struct phy_ops tegra124_pcie_phy_ops = {
+ .init = tegra124_pcie_phy_init,
+ .exit = tegra124_pcie_phy_exit,
+ .power_on = tegra124_pcie_phy_power_on,
+ .power_off = tegra124_pcie_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra124_pcie_pad_probe(struct tegra_xusb_padctl *padctl,
+ const struct tegra_xusb_pad_soc *soc,
+ struct device_node *np)
+{
+ struct tegra_xusb_pcie_pad *pcie;
+ struct tegra_xusb_pad *pad;
+ int err;
+
+ pcie = kzalloc(sizeof(*pcie), GFP_KERNEL);
+ if (!pcie)
+ return ERR_PTR(-ENOMEM);
+
+ pad = &pcie->base;
+ pad->ops = &tegra124_pcie_lane_ops;
+ pad->soc = soc;
+
+ err = tegra_xusb_pad_init(pad, padctl, np);
+ if (err < 0) {
+ kfree(pcie);
+ goto out;
+ }
+
+ err = tegra_xusb_pad_register(pad, &tegra124_pcie_phy_ops);
+ if (err < 0)
+ goto unregister;
+
+ dev_set_drvdata(&pad->dev, pad);
+
+ return pad;
+
+unregister:
+ device_unregister(&pad->dev);
+out:
+ return ERR_PTR(err);
+}
+
+static void tegra124_pcie_pad_remove(struct tegra_xusb_pad *pad)
+{
+ struct tegra_xusb_pcie_pad *pcie = to_pcie_pad(pad);
+
+ kfree(pcie);
+}
+
+static const struct tegra_xusb_pad_ops tegra124_pcie_ops = {
+ .probe = tegra124_pcie_pad_probe,
+ .remove = tegra124_pcie_pad_remove,
+};
+
+static const struct tegra_xusb_pad_soc tegra124_pcie_pad = {
+ .name = "pcie",
+ .num_lanes = ARRAY_SIZE(tegra124_pcie_lanes),
+ .lanes = tegra124_pcie_lanes,
+ .ops = &tegra124_pcie_ops,
+};
+
+static const struct tegra_xusb_lane_soc tegra124_sata_lanes[] = {
+ TEGRA124_LANE("sata-0", 0x134, 26, 0x3, pcie),
+};
+
+static struct tegra_xusb_lane *
+tegra124_sata_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+ unsigned int index)
+{
+ struct tegra_xusb_sata_lane *sata;
+ int err;
+
+ sata = kzalloc(sizeof(*sata), GFP_KERNEL);
+ if (!sata)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&sata->base.list);
+ sata->base.soc = &pad->soc->lanes[index];
+ sata->base.index = index;
+ sata->base.pad = pad;
+ sata->base.np = np;
+
+ err = tegra_xusb_lane_parse_dt(&sata->base, np);
+ if (err < 0) {
+ kfree(sata);
+ return ERR_PTR(err);
+ }
+
+ return &sata->base;
+}
+
+static void tegra124_sata_lane_remove(struct tegra_xusb_lane *lane)
+{
+ struct tegra_xusb_sata_lane *sata = to_sata_lane(lane);
+
+ kfree(sata);
+}
+
+static const struct tegra_xusb_lane_ops tegra124_sata_lane_ops = {
+ .probe = tegra124_sata_lane_probe,
+ .remove = tegra124_sata_lane_remove,
+};
+
+static int tegra124_sata_phy_init(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+ return tegra124_xusb_padctl_enable(lane->pad->padctl);
+}
+
+static int tegra124_sata_phy_exit(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+ return tegra124_xusb_padctl_disable(lane->pad->padctl);
+}
+
+static int tegra124_sata_phy_power_on(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ unsigned long timeout;
+ int err = -ETIMEDOUT;
+ u32 value;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1);
+ value &= ~XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD;
+ value &= ~XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ;
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+ value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD;
+ value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_IDDQ;
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+ value |= XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_MODE;
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+ value |= XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_RST;
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+
+ timeout = jiffies + msecs_to_jiffies(50);
+
+ while (time_before(jiffies, timeout)) {
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+ if (value & XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_LOCKDET) {
+ err = 0;
+ break;
+ }
+
+ usleep_range(100, 200);
+ }
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX);
+ value |= XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(lane->index);
+ padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX);
+
+ return err;
+}
+
+static int tegra124_sata_phy_power_off(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ u32 value;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX);
+ value &= ~XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(lane->index);
+ padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+ value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_RST;
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+ value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_MODE;
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+ value |= XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD;
+ value |= XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_IDDQ;
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1);
+ value |= ~XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD;
+ value |= ~XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ;
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1);
+
+ return 0;
+}
+
+static const struct phy_ops tegra124_sata_phy_ops = {
+ .init = tegra124_sata_phy_init,
+ .exit = tegra124_sata_phy_exit,
+ .power_on = tegra124_sata_phy_power_on,
+ .power_off = tegra124_sata_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra124_sata_pad_probe(struct tegra_xusb_padctl *padctl,
+ const struct tegra_xusb_pad_soc *soc,
+ struct device_node *np)
+{
+ struct tegra_xusb_sata_pad *sata;
+ struct tegra_xusb_pad *pad;
+ int err;
+
+ sata = kzalloc(sizeof(*sata), GFP_KERNEL);
+ if (!sata)
+ return ERR_PTR(-ENOMEM);
+
+ pad = &sata->base;
+ pad->ops = &tegra124_sata_lane_ops;
+ pad->soc = soc;
+
+ err = tegra_xusb_pad_init(pad, padctl, np);
+ if (err < 0) {
+ kfree(sata);
+ goto out;
+ }
+
+ err = tegra_xusb_pad_register(pad, &tegra124_sata_phy_ops);
+ if (err < 0)
+ goto unregister;
+
+ dev_set_drvdata(&pad->dev, pad);
+
+ return pad;
+
+unregister:
+ device_unregister(&pad->dev);
+out:
+ return ERR_PTR(err);
+}
+
+static void tegra124_sata_pad_remove(struct tegra_xusb_pad *pad)
+{
+ struct tegra_xusb_sata_pad *sata = to_sata_pad(pad);
+
+ kfree(sata);
+}
+
+static const struct tegra_xusb_pad_ops tegra124_sata_ops = {
+ .probe = tegra124_sata_pad_probe,
+ .remove = tegra124_sata_pad_remove,
+};
+
+static const struct tegra_xusb_pad_soc tegra124_sata_pad = {
+ .name = "sata",
+ .num_lanes = ARRAY_SIZE(tegra124_sata_lanes),
+ .lanes = tegra124_sata_lanes,
+ .ops = &tegra124_sata_ops,
+};
+
+static const struct tegra_xusb_pad_soc *tegra124_pads[] = {
+ &tegra124_usb2_pad,
+ &tegra124_ulpi_pad,
+ &tegra124_hsic_pad,
+ &tegra124_pcie_pad,
+ &tegra124_sata_pad,
+};
+
+static int tegra124_usb2_port_enable(struct tegra_xusb_port *port)
+{
+ return 0;
+}
+
+static void tegra124_usb2_port_disable(struct tegra_xusb_port *port)
+{
+}
+
+static struct tegra_xusb_lane *
+tegra124_usb2_port_map(struct tegra_xusb_port *port)
+{
+ return tegra_xusb_find_lane(port->padctl, "usb2", port->index);
+}
+
+static const struct tegra_xusb_port_ops tegra124_usb2_port_ops = {
+ .enable = tegra124_usb2_port_enable,
+ .disable = tegra124_usb2_port_disable,
+ .map = tegra124_usb2_port_map,
+};
+
+static int tegra124_ulpi_port_enable(struct tegra_xusb_port *port)
+{
+ return 0;
+}
+
+static void tegra124_ulpi_port_disable(struct tegra_xusb_port *port)
+{
+}
+
+static struct tegra_xusb_lane *
+tegra124_ulpi_port_map(struct tegra_xusb_port *port)
+{
+ return tegra_xusb_find_lane(port->padctl, "ulpi", port->index);
+}
+
+static const struct tegra_xusb_port_ops tegra124_ulpi_port_ops = {
+ .enable = tegra124_ulpi_port_enable,
+ .disable = tegra124_ulpi_port_disable,
+ .map = tegra124_ulpi_port_map,
+};
+
+static int tegra124_hsic_port_enable(struct tegra_xusb_port *port)
+{
+ return 0;
+}
+
+static void tegra124_hsic_port_disable(struct tegra_xusb_port *port)
+{
+}
+
+static struct tegra_xusb_lane *
+tegra124_hsic_port_map(struct tegra_xusb_port *port)
+{
+ return tegra_xusb_find_lane(port->padctl, "hsic", port->index);
+}
+
+static const struct tegra_xusb_port_ops tegra124_hsic_port_ops = {
+ .enable = tegra124_hsic_port_enable,
+ .disable = tegra124_hsic_port_disable,
+ .map = tegra124_hsic_port_map,
+};
+
+static int tegra124_usb3_port_enable(struct tegra_xusb_port *port)
+{
+ struct tegra_xusb_usb3_port *usb3 = to_usb3_port(port);
+ struct tegra_xusb_padctl *padctl = port->padctl;
+ struct tegra_xusb_lane *lane = usb3->base.lane;
+ unsigned int index = port->index, offset;
+ u32 value;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP);
+
+ if (!usb3->internal)
+ value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index);
+ else
+ value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index);
+
+ value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(index);
+ value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(index, usb3->port);
+ padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP);
+
+ /*
+ * TODO: move this code into the PCIe/SATA PHY ->power_on() callbacks
+ * and conditionalize based on mux function? This seems to work, but
+ * might not be the exact proper sequence.
+ */
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(index));
+ value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_MASK <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_SHIFT) |
+ (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_MASK <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_SHIFT) |
+ (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_MASK <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_SHIFT));
+ value |= (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_VAL <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_SHIFT) |
+ (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_VAL <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_SHIFT) |
+ (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_VAL <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_SHIFT);
+
+ if (usb3->context_saved) {
+ value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_MASK <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT) |
+ (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_MASK <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT));
+ value |= (usb3->ctle_g <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT) |
+ (usb3->ctle_z <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT);
+ }
+
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(index));
+
+ value = XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_VAL;
+
+ if (usb3->context_saved) {
+ value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_MASK <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT) |
+ (XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_MASK <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT));
+ value |= (usb3->tap1 <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT) |
+ (usb3->amp <<
+ XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT);
+ }
+
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(index));
+
+ if (lane->pad == padctl->pcie)
+ offset = XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL2(lane->index);
+ else
+ offset = XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL2;
+
+ value = padctl_readl(padctl, offset);
+ value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_MASK <<
+ XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_SHIFT);
+ value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_VAL <<
+ XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_SHIFT;
+ padctl_writel(padctl, value, offset);
+
+ if (lane->pad == padctl->pcie)
+ offset = XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL5(lane->index);
+ else
+ offset = XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL5;
+
+ value = padctl_readl(padctl, offset);
+ value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL5_RX_QEYE_EN;
+ padctl_writel(padctl, value, offset);
+
+ /* Enable SATA PHY when SATA lane is used */
+ if (lane->pad == padctl->sata) {
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+ value &= ~(XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_MASK <<
+ XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_SHIFT);
+ value |= 0x2 <<
+ XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_SHIFT;
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL2);
+ value &= ~((XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_MASK <<
+ XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_SHIFT) |
+ (XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_MASK <<
+ XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_SHIFT) |
+ (XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_MASK <<
+ XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_SHIFT) |
+ XUSB_PADCTL_IOPHY_PLL_S0_CTL2_TCLKOUT_EN);
+ value |= (0x7 <<
+ XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_SHIFT) |
+ (0x8 <<
+ XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_SHIFT) |
+ (0x8 <<
+ XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_SHIFT) |
+ XUSB_PADCTL_IOPHY_PLL_S0_CTL2_TXCLKREF_SEL;
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL2);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL3);
+ value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL3_RCAL_BYPASS;
+ padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL3);
+ }
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value &= ~XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_VCORE_DOWN(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value &= ~XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN_EARLY(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value &= ~XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+ return 0;
+}
+
+static void tegra124_usb3_port_disable(struct tegra_xusb_port *port)
+{
+ struct tegra_xusb_padctl *padctl = port->padctl;
+ u32 value;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value |= XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN_EARLY(port->index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value |= XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN(port->index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+ usleep_range(250, 350);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+ value |= XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_VCORE_DOWN(port->index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP);
+ value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(port->index);
+ value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(port->index, 0x7);
+ padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP);
+}
+
+static const struct tegra_xusb_lane_map tegra124_usb3_map[] = {
+ { 0, "pcie", 0 },
+ { 1, "pcie", 1 },
+ { 1, "sata", 0 },
+ { 0, NULL, 0 },
+};
+
+static struct tegra_xusb_lane *
+tegra124_usb3_port_map(struct tegra_xusb_port *port)
+{
+ return tegra_xusb_port_find_lane(port, tegra124_usb3_map, "usb3-ss");
+}
+
+static const struct tegra_xusb_port_ops tegra124_usb3_port_ops = {
+ .enable = tegra124_usb3_port_enable,
+ .disable = tegra124_usb3_port_disable,
+ .map = tegra124_usb3_port_map,
+};
+
+static int
+tegra124_xusb_read_fuse_calibration(struct tegra124_xusb_fuse_calibration *fuse)
+{
+ unsigned int i;
+ int err;
+ u32 value;
+
+ err = tegra_fuse_readl(TEGRA_FUSE_SKU_CALIB_0, &value);
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < ARRAY_SIZE(fuse->hs_curr_level); i++) {
+ fuse->hs_curr_level[i] =
+ (value >> FUSE_SKU_CALIB_HS_CURR_LEVEL_PADX_SHIFT(i)) &
+ FUSE_SKU_CALIB_HS_CURR_LEVEL_PAD_MASK;
+ }
+ fuse->hs_iref_cap =
+ (value >> FUSE_SKU_CALIB_HS_IREF_CAP_SHIFT) &
+ FUSE_SKU_CALIB_HS_IREF_CAP_MASK;
+ fuse->hs_term_range_adj =
+ (value >> FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_SHIFT) &
+ FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_MASK;
+ fuse->hs_squelch_level =
+ (value >> FUSE_SKU_CALIB_HS_SQUELCH_LEVEL_SHIFT) &
+ FUSE_SKU_CALIB_HS_SQUELCH_LEVEL_MASK;
+
+ return 0;
+}
+
+static struct tegra_xusb_padctl *
+tegra124_xusb_padctl_probe(struct device *dev,
+ const struct tegra_xusb_padctl_soc *soc)
+{
+ struct tegra124_xusb_padctl *padctl;
+ int err;
+
+ padctl = devm_kzalloc(dev, sizeof(*padctl), GFP_KERNEL);
+ if (!padctl)
+ return ERR_PTR(-ENOMEM);
+
+ padctl->base.dev = dev;
+ padctl->base.soc = soc;
+
+ err = tegra124_xusb_read_fuse_calibration(&padctl->fuse);
+ if (err < 0)
+ return ERR_PTR(err);
+
+ return &padctl->base;
+}
+
+static void tegra124_xusb_padctl_remove(struct tegra_xusb_padctl *padctl)
+{
+}
+
+static const struct tegra_xusb_padctl_ops tegra124_xusb_padctl_ops = {
+ .probe = tegra124_xusb_padctl_probe,
+ .remove = tegra124_xusb_padctl_remove,
+ .usb3_save_context = tegra124_usb3_save_context,
+ .hsic_set_idle = tegra124_hsic_set_idle,
+};
+
+const struct tegra_xusb_padctl_soc tegra124_xusb_padctl_soc = {
+ .num_pads = ARRAY_SIZE(tegra124_pads),
+ .pads = tegra124_pads,
+ .ports = {
+ .usb2 = {
+ .ops = &tegra124_usb2_port_ops,
+ .count = 3,
+ },
+ .ulpi = {
+ .ops = &tegra124_ulpi_port_ops,
+ .count = 1,
+ },
+ .hsic = {
+ .ops = &tegra124_hsic_port_ops,
+ .count = 2,
+ },
+ .usb3 = {
+ .ops = &tegra124_usb3_port_ops,
+ .count = 2,
+ },
+ },
+ .ops = &tegra124_xusb_padctl_ops,
+};
+EXPORT_SYMBOL_GPL(tegra124_xusb_padctl_soc);
+
+MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
+MODULE_DESCRIPTION("NVIDIA Tegra 124 XUSB Pad Controller driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/tegra/xusb-tegra210.c b/drivers/phy/tegra/xusb-tegra210.c
new file mode 100644
index 000000000..05bee32a3
--- /dev/null
+++ b/drivers/phy/tegra/xusb-tegra210.c
@@ -0,0 +1,2043 @@
+/*
+ * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved.
+ * Copyright (C) 2015 Google, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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/tegra.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+
+#include <soc/tegra/fuse.h>
+
+#include "xusb.h"
+
+#define FUSE_SKU_CALIB_HS_CURR_LEVEL_PADX_SHIFT(x) \
+ ((x) ? (11 + ((x) - 1) * 6) : 0)
+#define FUSE_SKU_CALIB_HS_CURR_LEVEL_PAD_MASK 0x3f
+#define FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_SHIFT 7
+#define FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_MASK 0xf
+
+#define FUSE_USB_CALIB_EXT_RPD_CTRL_SHIFT 0
+#define FUSE_USB_CALIB_EXT_RPD_CTRL_MASK 0x1f
+
+#define XUSB_PADCTL_USB2_PAD_MUX 0x004
+#define XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_SHIFT 16
+#define XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_MASK 0x3
+#define XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_XUSB 0x1
+#define XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_SHIFT 18
+#define XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_MASK 0x3
+#define XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_XUSB 0x1
+
+#define XUSB_PADCTL_USB2_PORT_CAP 0x008
+#define XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_HOST(x) (0x1 << ((x) * 4))
+#define XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_MASK(x) (0x3 << ((x) * 4))
+
+#define XUSB_PADCTL_SS_PORT_MAP 0x014
+#define XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(x) (1 << (((x) * 5) + 4))
+#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_SHIFT(x) ((x) * 5)
+#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(x) (0x7 << ((x) * 5))
+#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(x, v) (((v) & 0x7) << ((x) * 5))
+
+#define XUSB_PADCTL_ELPG_PROGRAM1 0x024
+#define XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_VCORE_DOWN (1 << 31)
+#define XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN_EARLY (1 << 30)
+#define XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN (1 << 29)
+#define XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_VCORE_DOWN(x) (1 << (2 + (x) * 3))
+#define XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(x) \
+ (1 << (1 + (x) * 3))
+#define XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(x) (1 << ((x) * 3))
+
+#define XUSB_PADCTL_USB3_PAD_MUX 0x028
+#define XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(x) (1 << (1 + (x)))
+#define XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(x) (1 << (8 + (x)))
+
+#define XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADX_CTL1(x) (0x084 + (x) * 0x40)
+#define XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_LEV_SHIFT 7
+#define XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_LEV_MASK 0x3
+#define XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_FIX18 (1 << 6)
+
+#define XUSB_PADCTL_USB2_OTG_PADX_CTL0(x) (0x088 + (x) * 0x40)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD_ZI (1 << 29)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD2 (1 << 27)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD (1 << 26)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT 0
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_MASK 0x3f
+
+#define XUSB_PADCTL_USB2_OTG_PADX_CTL1(x) (0x08c + (x) * 0x40)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_SHIFT 26
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_MASK 0x1f
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT 3
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_MASK 0xf
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR (1 << 2)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DISC_OVRD (1 << 1)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_CHRP_OVRD (1 << 0)
+
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0 0x284
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD (1 << 11)
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT 3
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_MASK 0x7
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_VAL 0x7
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT 0
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_MASK 0x7
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_VAL 0x2
+
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1 0x288
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_PD_TRK (1 << 26)
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_SHIFT 19
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_MASK 0x7f
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_VAL 0x0a
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_SHIFT 12
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_MASK 0x7f
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_VAL 0x1e
+
+#define XUSB_PADCTL_HSIC_PADX_CTL0(x) (0x300 + (x) * 0x20)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_RPU_STROBE (1 << 18)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA1 (1 << 17)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA0 (1 << 16)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_RPD_STROBE (1 << 15)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA1 (1 << 14)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA0 (1 << 13)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_STROBE (1 << 9)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA1 (1 << 8)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA0 (1 << 7)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_STROBE (1 << 6)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA1 (1 << 5)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA0 (1 << 4)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_STROBE (1 << 3)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA1 (1 << 2)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA0 (1 << 1)
+
+#define XUSB_PADCTL_HSIC_PADX_CTL1(x) (0x304 + (x) * 0x20)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_SHIFT 0
+#define XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_MASK 0xf
+
+#define XUSB_PADCTL_HSIC_PADX_CTL2(x) (0x308 + (x) * 0x20)
+#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT 8
+#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK 0xf
+#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT 0
+#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK 0xff
+
+#define XUSB_PADCTL_HSIC_PAD_TRK_CTL 0x340
+#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_PD_TRK (1 << 19)
+#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_SHIFT 12
+#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_MASK 0x7f
+#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_VAL 0x0a
+#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_SHIFT 5
+#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_MASK 0x7f
+#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_VAL 0x1e
+
+#define XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL 0x344
+
+#define XUSB_PADCTL_UPHY_PLL_P0_CTL1 0x360
+#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT 20
+#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_MASK 0xff
+#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_USB_VAL 0x19
+#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SATA_VAL 0x1e
+#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_SHIFT 16
+#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_MASK 0x3
+#define XUSB_PADCTL_UPHY_PLL_CTL1_LOCKDET_STATUS (1 << 15)
+#define XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD (1 << 4)
+#define XUSB_PADCTL_UPHY_PLL_CTL1_ENABLE (1 << 3)
+#define XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_SHIFT 1
+#define XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_MASK 0x3
+#define XUSB_PADCTL_UPHY_PLL_CTL1_IDDQ (1 << 0)
+
+#define XUSB_PADCTL_UPHY_PLL_P0_CTL2 0x364
+#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT 4
+#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_MASK 0xffffff
+#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_VAL 0x136
+#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD (1 << 2)
+#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE (1 << 1)
+#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN (1 << 0)
+
+#define XUSB_PADCTL_UPHY_PLL_P0_CTL4 0x36c
+#define XUSB_PADCTL_UPHY_PLL_CTL4_XDIGCLK_EN (1 << 19)
+#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_EN (1 << 15)
+#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT 12
+#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_MASK 0x3
+#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_USB_VAL 0x2
+#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SATA_VAL 0x0
+#define XUSB_PADCTL_UPHY_PLL_CTL4_REFCLKBUF_EN (1 << 8)
+#define XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_SHIFT 4
+#define XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_MASK 0xf
+
+#define XUSB_PADCTL_UPHY_PLL_P0_CTL5 0x370
+#define XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT 16
+#define XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_MASK 0xff
+#define XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_VAL 0x2a
+
+#define XUSB_PADCTL_UPHY_PLL_P0_CTL8 0x37c
+#define XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE (1 << 31)
+#define XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD (1 << 15)
+#define XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN (1 << 13)
+#define XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN (1 << 12)
+
+#define XUSB_PADCTL_UPHY_MISC_PAD_PX_CTL1(x) (0x460 + (x) * 0x40)
+#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_SHIFT 20
+#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_MASK 0x3
+#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_VAL 0x1
+#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_TERM_EN BIT(18)
+#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_MODE_OVRD BIT(13)
+
+#define XUSB_PADCTL_UPHY_PLL_S0_CTL1 0x860
+
+#define XUSB_PADCTL_UPHY_PLL_S0_CTL2 0x864
+
+#define XUSB_PADCTL_UPHY_PLL_S0_CTL4 0x86c
+
+#define XUSB_PADCTL_UPHY_PLL_S0_CTL5 0x870
+
+#define XUSB_PADCTL_UPHY_PLL_S0_CTL8 0x87c
+
+#define XUSB_PADCTL_UPHY_MISC_PAD_S0_CTL1 0x960
+
+#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL1(x) (0xa60 + (x) * 0x40)
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_SHIFT 16
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_MASK 0x3
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_VAL 0x2
+
+#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL2(x) (0xa64 + (x) * 0x40)
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_SHIFT 0
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_MASK 0xffff
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_VAL 0x00fc
+
+#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL3(x) (0xa68 + (x) * 0x40)
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL3_RX_DFE_VAL 0xc0077f1f
+
+#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL4(x) (0xa6c + (x) * 0x40)
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_SHIFT 16
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_MASK 0xffff
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_VAL 0x01c7
+
+#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL6(x) (0xa74 + (x) * 0x40)
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL6_RX_EQ_CTRL_H_VAL 0xfcf01368
+
+struct tegra210_xusb_fuse_calibration {
+ u32 hs_curr_level[4];
+ u32 hs_term_range_adj;
+ u32 rpd_ctrl;
+};
+
+struct tegra210_xusb_padctl {
+ struct tegra_xusb_padctl base;
+
+ struct tegra210_xusb_fuse_calibration fuse;
+};
+
+static inline struct tegra210_xusb_padctl *
+to_tegra210_xusb_padctl(struct tegra_xusb_padctl *padctl)
+{
+ return container_of(padctl, struct tegra210_xusb_padctl, base);
+}
+
+/* must be called under padctl->lock */
+static int tegra210_pex_uphy_enable(struct tegra_xusb_padctl *padctl)
+{
+ struct tegra_xusb_pcie_pad *pcie = to_pcie_pad(padctl->pcie);
+ unsigned long timeout;
+ u32 value;
+ int err;
+
+ if (pcie->enable > 0) {
+ pcie->enable++;
+ return 0;
+ }
+
+ err = clk_prepare_enable(pcie->pll);
+ if (err < 0)
+ return err;
+
+ err = reset_control_deassert(pcie->rst);
+ if (err < 0)
+ goto disable;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+ value &= ~(XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_MASK <<
+ XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT);
+ value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_VAL <<
+ XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL5);
+ value &= ~(XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_MASK <<
+ XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT);
+ value |= XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_VAL <<
+ XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL5);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+ value |= XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+ value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+ value |= XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL4);
+ value &= ~((XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_MASK <<
+ XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT) |
+ (XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_MASK <<
+ XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_SHIFT));
+ value |= (XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_USB_VAL <<
+ XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT) |
+ XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_EN;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL4);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+ value &= ~((XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_MASK <<
+ XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_SHIFT) |
+ (XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_MASK <<
+ XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT));
+ value |= XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_USB_VAL <<
+ XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+ value &= ~XUSB_PADCTL_UPHY_PLL_CTL1_IDDQ;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+ value &= ~(XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_MASK <<
+ XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_SHIFT);
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+
+ usleep_range(10, 20);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL4);
+ value |= XUSB_PADCTL_UPHY_PLL_CTL4_REFCLKBUF_EN;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL4);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+ value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+
+ timeout = jiffies + msecs_to_jiffies(100);
+
+ while (time_before(jiffies, timeout)) {
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+ if (value & XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE)
+ break;
+
+ usleep_range(10, 20);
+ }
+
+ if (time_after_eq(jiffies, timeout)) {
+ err = -ETIMEDOUT;
+ goto reset;
+ }
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+ value &= ~XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+
+ timeout = jiffies + msecs_to_jiffies(100);
+
+ while (time_before(jiffies, timeout)) {
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+ if (!(value & XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE))
+ break;
+
+ usleep_range(10, 20);
+ }
+
+ if (time_after_eq(jiffies, timeout)) {
+ err = -ETIMEDOUT;
+ goto reset;
+ }
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+ value |= XUSB_PADCTL_UPHY_PLL_CTL1_ENABLE;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+
+ timeout = jiffies + msecs_to_jiffies(100);
+
+ while (time_before(jiffies, timeout)) {
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+ if (value & XUSB_PADCTL_UPHY_PLL_CTL1_LOCKDET_STATUS)
+ break;
+
+ usleep_range(10, 20);
+ }
+
+ if (time_after_eq(jiffies, timeout)) {
+ err = -ETIMEDOUT;
+ goto reset;
+ }
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+ value |= XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN |
+ XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+
+ timeout = jiffies + msecs_to_jiffies(100);
+
+ while (time_before(jiffies, timeout)) {
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+ if (value & XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE)
+ break;
+
+ usleep_range(10, 20);
+ }
+
+ if (time_after_eq(jiffies, timeout)) {
+ err = -ETIMEDOUT;
+ goto reset;
+ }
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+ value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+
+ timeout = jiffies + msecs_to_jiffies(100);
+
+ while (time_before(jiffies, timeout)) {
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+ if (!(value & XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE))
+ break;
+
+ usleep_range(10, 20);
+ }
+
+ if (time_after_eq(jiffies, timeout)) {
+ err = -ETIMEDOUT;
+ goto reset;
+ }
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+ value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+
+ tegra210_xusb_pll_hw_control_enable();
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+ value &= ~XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+ value &= ~XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+ value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+
+ usleep_range(10, 20);
+
+ tegra210_xusb_pll_hw_sequence_start();
+
+ pcie->enable++;
+
+ return 0;
+
+reset:
+ reset_control_assert(pcie->rst);
+disable:
+ clk_disable_unprepare(pcie->pll);
+ return err;
+}
+
+static void tegra210_pex_uphy_disable(struct tegra_xusb_padctl *padctl)
+{
+ struct tegra_xusb_pcie_pad *pcie = to_pcie_pad(padctl->pcie);
+
+ mutex_lock(&padctl->lock);
+
+ if (WARN_ON(pcie->enable == 0))
+ goto unlock;
+
+ if (--pcie->enable > 0)
+ goto unlock;
+
+ reset_control_assert(pcie->rst);
+ clk_disable_unprepare(pcie->pll);
+
+unlock:
+ mutex_unlock(&padctl->lock);
+}
+
+/* must be called under padctl->lock */
+static int tegra210_sata_uphy_enable(struct tegra_xusb_padctl *padctl, bool usb)
+{
+ struct tegra_xusb_sata_pad *sata = to_sata_pad(padctl->sata);
+ unsigned long timeout;
+ u32 value;
+ int err;
+
+ if (sata->enable > 0) {
+ sata->enable++;
+ return 0;
+ }
+
+ err = clk_prepare_enable(sata->pll);
+ if (err < 0)
+ return err;
+
+ err = reset_control_deassert(sata->rst);
+ if (err < 0)
+ goto disable;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+ value &= ~(XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_MASK <<
+ XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT);
+ value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_VAL <<
+ XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL5);
+ value &= ~(XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_MASK <<
+ XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT);
+ value |= XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_VAL <<
+ XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL5);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+ value |= XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+ value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+ value |= XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL4);
+ value &= ~((XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_MASK <<
+ XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT) |
+ (XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_MASK <<
+ XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_SHIFT));
+ value |= XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_EN;
+
+ if (usb)
+ value |= (XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_USB_VAL <<
+ XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT);
+ else
+ value |= (XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SATA_VAL <<
+ XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT);
+
+ value &= ~XUSB_PADCTL_UPHY_PLL_CTL4_XDIGCLK_EN;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL4);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+ value &= ~((XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_MASK <<
+ XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_SHIFT) |
+ (XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_MASK <<
+ XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT));
+
+ if (usb)
+ value |= XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_USB_VAL <<
+ XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT;
+ else
+ value |= XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SATA_VAL <<
+ XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT;
+
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+ value &= ~XUSB_PADCTL_UPHY_PLL_CTL1_IDDQ;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+ value &= ~(XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_MASK <<
+ XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_SHIFT);
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+
+ usleep_range(10, 20);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL4);
+ value |= XUSB_PADCTL_UPHY_PLL_CTL4_REFCLKBUF_EN;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL4);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+ value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+
+ timeout = jiffies + msecs_to_jiffies(100);
+
+ while (time_before(jiffies, timeout)) {
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+ if (value & XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE)
+ break;
+
+ usleep_range(10, 20);
+ }
+
+ if (time_after_eq(jiffies, timeout)) {
+ err = -ETIMEDOUT;
+ goto reset;
+ }
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+ value &= ~XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+
+ timeout = jiffies + msecs_to_jiffies(100);
+
+ while (time_before(jiffies, timeout)) {
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+ if (!(value & XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE))
+ break;
+
+ usleep_range(10, 20);
+ }
+
+ if (time_after_eq(jiffies, timeout)) {
+ err = -ETIMEDOUT;
+ goto reset;
+ }
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+ value |= XUSB_PADCTL_UPHY_PLL_CTL1_ENABLE;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+
+ timeout = jiffies + msecs_to_jiffies(100);
+
+ while (time_before(jiffies, timeout)) {
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+ if (value & XUSB_PADCTL_UPHY_PLL_CTL1_LOCKDET_STATUS)
+ break;
+
+ usleep_range(10, 20);
+ }
+
+ if (time_after_eq(jiffies, timeout)) {
+ err = -ETIMEDOUT;
+ goto reset;
+ }
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+ value |= XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN |
+ XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+
+ timeout = jiffies + msecs_to_jiffies(100);
+
+ while (time_before(jiffies, timeout)) {
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+ if (value & XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE)
+ break;
+
+ usleep_range(10, 20);
+ }
+
+ if (time_after_eq(jiffies, timeout)) {
+ err = -ETIMEDOUT;
+ goto reset;
+ }
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+ value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+
+ timeout = jiffies + msecs_to_jiffies(100);
+
+ while (time_before(jiffies, timeout)) {
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+ if (!(value & XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE))
+ break;
+
+ usleep_range(10, 20);
+ }
+
+ if (time_after_eq(jiffies, timeout)) {
+ err = -ETIMEDOUT;
+ goto reset;
+ }
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+ value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+
+ tegra210_sata_pll_hw_control_enable();
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+ value &= ~XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+ value &= ~XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+ value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+
+ usleep_range(10, 20);
+
+ tegra210_sata_pll_hw_sequence_start();
+
+ sata->enable++;
+
+ return 0;
+
+reset:
+ reset_control_assert(sata->rst);
+disable:
+ clk_disable_unprepare(sata->pll);
+ return err;
+}
+
+static void tegra210_sata_uphy_disable(struct tegra_xusb_padctl *padctl)
+{
+ struct tegra_xusb_sata_pad *sata = to_sata_pad(padctl->sata);
+
+ mutex_lock(&padctl->lock);
+
+ if (WARN_ON(sata->enable == 0))
+ goto unlock;
+
+ if (--sata->enable > 0)
+ goto unlock;
+
+ reset_control_assert(sata->rst);
+ clk_disable_unprepare(sata->pll);
+
+unlock:
+ mutex_unlock(&padctl->lock);
+}
+
+static int tegra210_xusb_padctl_enable(struct tegra_xusb_padctl *padctl)
+{
+ u32 value;
+
+ mutex_lock(&padctl->lock);
+
+ if (padctl->enable++ > 0)
+ goto out;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+ value &= ~XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN;
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+ value &= ~XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN_EARLY;
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+ value &= ~XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_VCORE_DOWN;
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+out:
+ mutex_unlock(&padctl->lock);
+ return 0;
+}
+
+static int tegra210_xusb_padctl_disable(struct tegra_xusb_padctl *padctl)
+{
+ u32 value;
+
+ mutex_lock(&padctl->lock);
+
+ if (WARN_ON(padctl->enable == 0))
+ goto out;
+
+ if (--padctl->enable > 0)
+ goto out;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+ value |= XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_VCORE_DOWN;
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+ value |= XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN_EARLY;
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+ value |= XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN;
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+out:
+ mutex_unlock(&padctl->lock);
+ return 0;
+}
+
+static int tegra210_hsic_set_idle(struct tegra_xusb_padctl *padctl,
+ unsigned int index, bool idle)
+{
+ u32 value;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(index));
+
+ value &= ~(XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA0 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA1 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_RPD_STROBE);
+
+ if (idle)
+ value |= XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA0 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA1 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_RPU_STROBE;
+ else
+ value &= ~(XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA0 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA1 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_RPU_STROBE);
+
+ padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL0(index));
+
+ return 0;
+}
+
+static int tegra210_usb3_set_lfps_detect(struct tegra_xusb_padctl *padctl,
+ unsigned int index, bool enable)
+{
+ struct tegra_xusb_port *port;
+ struct tegra_xusb_lane *lane;
+ u32 value, offset;
+
+ port = tegra_xusb_find_port(padctl, "usb3", index);
+ if (!port)
+ return -ENODEV;
+
+ lane = port->lane;
+
+ if (lane->pad == padctl->pcie)
+ offset = XUSB_PADCTL_UPHY_MISC_PAD_PX_CTL1(lane->index);
+ else
+ offset = XUSB_PADCTL_UPHY_MISC_PAD_S0_CTL1;
+
+ value = padctl_readl(padctl, offset);
+
+ value &= ~((XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_MASK <<
+ XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_SHIFT) |
+ XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_TERM_EN |
+ XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_MODE_OVRD);
+
+ if (!enable) {
+ value |= (XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_VAL <<
+ XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_SHIFT) |
+ XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_TERM_EN |
+ XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_MODE_OVRD;
+ }
+
+ padctl_writel(padctl, value, offset);
+
+ return 0;
+}
+
+#define TEGRA210_LANE(_name, _offset, _shift, _mask, _type) \
+ { \
+ .name = _name, \
+ .offset = _offset, \
+ .shift = _shift, \
+ .mask = _mask, \
+ .num_funcs = ARRAY_SIZE(tegra210_##_type##_functions), \
+ .funcs = tegra210_##_type##_functions, \
+ }
+
+static const char *tegra210_usb2_functions[] = {
+ "snps",
+ "xusb",
+ "uart"
+};
+
+static const struct tegra_xusb_lane_soc tegra210_usb2_lanes[] = {
+ TEGRA210_LANE("usb2-0", 0x004, 0, 0x3, usb2),
+ TEGRA210_LANE("usb2-1", 0x004, 2, 0x3, usb2),
+ TEGRA210_LANE("usb2-2", 0x004, 4, 0x3, usb2),
+ TEGRA210_LANE("usb2-3", 0x004, 6, 0x3, usb2),
+};
+
+static struct tegra_xusb_lane *
+tegra210_usb2_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+ unsigned int index)
+{
+ struct tegra_xusb_usb2_lane *usb2;
+ int err;
+
+ usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL);
+ if (!usb2)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&usb2->base.list);
+ usb2->base.soc = &pad->soc->lanes[index];
+ usb2->base.index = index;
+ usb2->base.pad = pad;
+ usb2->base.np = np;
+
+ err = tegra_xusb_lane_parse_dt(&usb2->base, np);
+ if (err < 0) {
+ kfree(usb2);
+ return ERR_PTR(err);
+ }
+
+ return &usb2->base;
+}
+
+static void tegra210_usb2_lane_remove(struct tegra_xusb_lane *lane)
+{
+ struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane);
+
+ kfree(usb2);
+}
+
+static const struct tegra_xusb_lane_ops tegra210_usb2_lane_ops = {
+ .probe = tegra210_usb2_lane_probe,
+ .remove = tegra210_usb2_lane_remove,
+};
+
+static int tegra210_usb2_phy_init(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ u32 value;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_PAD_MUX);
+ value &= ~(XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_MASK <<
+ XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_SHIFT);
+ value |= XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_XUSB <<
+ XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_SHIFT;
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_PAD_MUX);
+
+ return tegra210_xusb_padctl_enable(padctl);
+}
+
+static int tegra210_usb2_phy_exit(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+ return tegra210_xusb_padctl_disable(lane->pad->padctl);
+}
+
+static int tegra210_usb2_phy_power_on(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane);
+ struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ struct tegra210_xusb_padctl *priv;
+ struct tegra_xusb_usb2_port *port;
+ unsigned int index = lane->index;
+ u32 value;
+ int err;
+
+ port = tegra_xusb_find_usb2_port(padctl, index);
+ if (!port) {
+ dev_err(&phy->dev, "no port found for USB2 lane %u\n", index);
+ return -ENODEV;
+ }
+
+ priv = to_tegra210_xusb_padctl(padctl);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+ value &= ~((XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_MASK <<
+ XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT) |
+ (XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_MASK <<
+ XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT));
+ value |= (XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_VAL <<
+ XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT);
+
+ if (tegra_sku_info.revision < TEGRA_REVISION_A02)
+ value |=
+ (XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_VAL <<
+ XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT);
+
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_PORT_CAP);
+ value &= ~XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_MASK(index);
+ value |= XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_HOST(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_PORT_CAP);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
+ value &= ~((XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_MASK <<
+ XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT) |
+ XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD |
+ XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD2 |
+ XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD_ZI);
+ value |= (priv->fuse.hs_curr_level[index] +
+ usb2->hs_curr_level_offset) <<
+ XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT;
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
+ value &= ~((XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_MASK <<
+ XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT) |
+ (XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_MASK <<
+ XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_SHIFT) |
+ XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR |
+ XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_CHRP_OVRD |
+ XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DISC_OVRD);
+ value |= (priv->fuse.hs_term_range_adj <<
+ XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT) |
+ (priv->fuse.rpd_ctrl <<
+ XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_SHIFT);
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
+
+ value = padctl_readl(padctl,
+ XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADX_CTL1(index));
+ value &= ~(XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_LEV_MASK <<
+ XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_LEV_SHIFT);
+ value |= XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_FIX18;
+ padctl_writel(padctl, value,
+ XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADX_CTL1(index));
+
+ err = regulator_enable(port->supply);
+ if (err)
+ return err;
+
+ mutex_lock(&padctl->lock);
+
+ if (pad->enable > 0) {
+ pad->enable++;
+ mutex_unlock(&padctl->lock);
+ return 0;
+ }
+
+ err = clk_prepare_enable(pad->clk);
+ if (err)
+ goto disable_regulator;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
+ value &= ~((XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_MASK <<
+ XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_SHIFT) |
+ (XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_MASK <<
+ XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_SHIFT));
+ value |= (XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_VAL <<
+ XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_SHIFT) |
+ (XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_VAL <<
+ XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_SHIFT);
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+ value &= ~XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD;
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+
+ udelay(1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
+ value &= ~XUSB_PADCTL_USB2_BIAS_PAD_CTL1_PD_TRK;
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
+
+ udelay(50);
+
+ clk_disable_unprepare(pad->clk);
+
+ pad->enable++;
+ mutex_unlock(&padctl->lock);
+
+ return 0;
+
+disable_regulator:
+ regulator_disable(port->supply);
+ mutex_unlock(&padctl->lock);
+ return err;
+}
+
+static int tegra210_usb2_phy_power_off(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ struct tegra_xusb_usb2_port *port;
+ u32 value;
+
+ port = tegra_xusb_find_usb2_port(padctl, lane->index);
+ if (!port) {
+ dev_err(&phy->dev, "no port found for USB2 lane %u\n",
+ lane->index);
+ return -ENODEV;
+ }
+
+ mutex_lock(&padctl->lock);
+
+ if (WARN_ON(pad->enable == 0))
+ goto out;
+
+ if (--pad->enable > 0)
+ goto out;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+ value |= XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD;
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+
+out:
+ regulator_disable(port->supply);
+ mutex_unlock(&padctl->lock);
+ return 0;
+}
+
+static const struct phy_ops tegra210_usb2_phy_ops = {
+ .init = tegra210_usb2_phy_init,
+ .exit = tegra210_usb2_phy_exit,
+ .power_on = tegra210_usb2_phy_power_on,
+ .power_off = tegra210_usb2_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra210_usb2_pad_probe(struct tegra_xusb_padctl *padctl,
+ const struct tegra_xusb_pad_soc *soc,
+ struct device_node *np)
+{
+ struct tegra_xusb_usb2_pad *usb2;
+ struct tegra_xusb_pad *pad;
+ int err;
+
+ usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL);
+ if (!usb2)
+ return ERR_PTR(-ENOMEM);
+
+ pad = &usb2->base;
+ pad->ops = &tegra210_usb2_lane_ops;
+ pad->soc = soc;
+
+ err = tegra_xusb_pad_init(pad, padctl, np);
+ if (err < 0) {
+ kfree(usb2);
+ goto out;
+ }
+
+ usb2->clk = devm_clk_get(&pad->dev, "trk");
+ if (IS_ERR(usb2->clk)) {
+ err = PTR_ERR(usb2->clk);
+ dev_err(&pad->dev, "failed to get trk clock: %d\n", err);
+ goto unregister;
+ }
+
+ err = tegra_xusb_pad_register(pad, &tegra210_usb2_phy_ops);
+ if (err < 0)
+ goto unregister;
+
+ dev_set_drvdata(&pad->dev, pad);
+
+ return pad;
+
+unregister:
+ device_unregister(&pad->dev);
+out:
+ return ERR_PTR(err);
+}
+
+static void tegra210_usb2_pad_remove(struct tegra_xusb_pad *pad)
+{
+ struct tegra_xusb_usb2_pad *usb2 = to_usb2_pad(pad);
+
+ kfree(usb2);
+}
+
+static const struct tegra_xusb_pad_ops tegra210_usb2_ops = {
+ .probe = tegra210_usb2_pad_probe,
+ .remove = tegra210_usb2_pad_remove,
+};
+
+static const struct tegra_xusb_pad_soc tegra210_usb2_pad = {
+ .name = "usb2",
+ .num_lanes = ARRAY_SIZE(tegra210_usb2_lanes),
+ .lanes = tegra210_usb2_lanes,
+ .ops = &tegra210_usb2_ops,
+};
+
+static const char *tegra210_hsic_functions[] = {
+ "snps",
+ "xusb",
+};
+
+static const struct tegra_xusb_lane_soc tegra210_hsic_lanes[] = {
+ TEGRA210_LANE("hsic-0", 0x004, 14, 0x1, hsic),
+};
+
+static struct tegra_xusb_lane *
+tegra210_hsic_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+ unsigned int index)
+{
+ struct tegra_xusb_hsic_lane *hsic;
+ int err;
+
+ hsic = kzalloc(sizeof(*hsic), GFP_KERNEL);
+ if (!hsic)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&hsic->base.list);
+ hsic->base.soc = &pad->soc->lanes[index];
+ hsic->base.index = index;
+ hsic->base.pad = pad;
+ hsic->base.np = np;
+
+ err = tegra_xusb_lane_parse_dt(&hsic->base, np);
+ if (err < 0) {
+ kfree(hsic);
+ return ERR_PTR(err);
+ }
+
+ return &hsic->base;
+}
+
+static void tegra210_hsic_lane_remove(struct tegra_xusb_lane *lane)
+{
+ struct tegra_xusb_hsic_lane *hsic = to_hsic_lane(lane);
+
+ kfree(hsic);
+}
+
+static const struct tegra_xusb_lane_ops tegra210_hsic_lane_ops = {
+ .probe = tegra210_hsic_lane_probe,
+ .remove = tegra210_hsic_lane_remove,
+};
+
+static int tegra210_hsic_phy_init(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ u32 value;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_PAD_MUX);
+ value &= ~(XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_MASK <<
+ XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_SHIFT);
+ value |= XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_XUSB <<
+ XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_SHIFT;
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_PAD_MUX);
+
+ return tegra210_xusb_padctl_enable(padctl);
+}
+
+static int tegra210_hsic_phy_exit(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+ return tegra210_xusb_padctl_disable(lane->pad->padctl);
+}
+
+static int tegra210_hsic_phy_power_on(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_hsic_lane *hsic = to_hsic_lane(lane);
+ struct tegra_xusb_hsic_pad *pad = to_hsic_pad(lane->pad);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ struct tegra210_xusb_padctl *priv;
+ unsigned int index = lane->index;
+ u32 value;
+ int err;
+
+ priv = to_tegra210_xusb_padctl(padctl);
+
+ err = regulator_enable(pad->supply);
+ if (err)
+ return err;
+
+ padctl_writel(padctl, hsic->strobe_trim,
+ XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+ value &= ~(XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_MASK <<
+ XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_SHIFT);
+ value |= (hsic->tx_rtune_p <<
+ XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_SHIFT);
+ padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+
+ value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL2(index));
+ value &= ~((XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK <<
+ XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT) |
+ (XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK <<
+ XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT));
+ value |= (hsic->rx_strobe_trim <<
+ XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT) |
+ (hsic->rx_data_trim <<
+ XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT);
+ padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL2(index));
+
+ value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(index));
+ value &= ~(XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA0 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA1 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_RPU_STROBE |
+ XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA0 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA1 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_STROBE |
+ XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA0 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA1 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_STROBE |
+ XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA0 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA1 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_STROBE);
+ value |= XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA0 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA1 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_RPD_STROBE;
+ padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL0(index));
+
+ err = clk_prepare_enable(pad->clk);
+ if (err)
+ goto disable;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PAD_TRK_CTL);
+ value &= ~((XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_MASK <<
+ XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_SHIFT) |
+ (XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_MASK <<
+ XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_SHIFT));
+ value |= (XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_VAL <<
+ XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_SHIFT) |
+ (XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_VAL <<
+ XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_SHIFT);
+ padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PAD_TRK_CTL);
+
+ udelay(1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PAD_TRK_CTL);
+ value &= ~XUSB_PADCTL_HSIC_PAD_TRK_CTL_PD_TRK;
+ padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PAD_TRK_CTL);
+
+ udelay(50);
+
+ clk_disable_unprepare(pad->clk);
+
+ return 0;
+
+disable:
+ regulator_disable(pad->supply);
+ return err;
+}
+
+static int tegra210_hsic_phy_power_off(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_hsic_pad *pad = to_hsic_pad(lane->pad);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ unsigned int index = lane->index;
+ u32 value;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(index));
+ value |= XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA0 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA1 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_STROBE |
+ XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA0 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA1 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_STROBE |
+ XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA0 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA1 |
+ XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_STROBE;
+ padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+
+ regulator_disable(pad->supply);
+
+ return 0;
+}
+
+static const struct phy_ops tegra210_hsic_phy_ops = {
+ .init = tegra210_hsic_phy_init,
+ .exit = tegra210_hsic_phy_exit,
+ .power_on = tegra210_hsic_phy_power_on,
+ .power_off = tegra210_hsic_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra210_hsic_pad_probe(struct tegra_xusb_padctl *padctl,
+ const struct tegra_xusb_pad_soc *soc,
+ struct device_node *np)
+{
+ struct tegra_xusb_hsic_pad *hsic;
+ struct tegra_xusb_pad *pad;
+ int err;
+
+ hsic = kzalloc(sizeof(*hsic), GFP_KERNEL);
+ if (!hsic)
+ return ERR_PTR(-ENOMEM);
+
+ pad = &hsic->base;
+ pad->ops = &tegra210_hsic_lane_ops;
+ pad->soc = soc;
+
+ err = tegra_xusb_pad_init(pad, padctl, np);
+ if (err < 0) {
+ kfree(hsic);
+ goto out;
+ }
+
+ hsic->clk = devm_clk_get(&pad->dev, "trk");
+ if (IS_ERR(hsic->clk)) {
+ err = PTR_ERR(hsic->clk);
+ dev_err(&pad->dev, "failed to get trk clock: %d\n", err);
+ goto unregister;
+ }
+
+ err = tegra_xusb_pad_register(pad, &tegra210_hsic_phy_ops);
+ if (err < 0)
+ goto unregister;
+
+ dev_set_drvdata(&pad->dev, pad);
+
+ return pad;
+
+unregister:
+ device_unregister(&pad->dev);
+out:
+ return ERR_PTR(err);
+}
+
+static void tegra210_hsic_pad_remove(struct tegra_xusb_pad *pad)
+{
+ struct tegra_xusb_hsic_pad *hsic = to_hsic_pad(pad);
+
+ kfree(hsic);
+}
+
+static const struct tegra_xusb_pad_ops tegra210_hsic_ops = {
+ .probe = tegra210_hsic_pad_probe,
+ .remove = tegra210_hsic_pad_remove,
+};
+
+static const struct tegra_xusb_pad_soc tegra210_hsic_pad = {
+ .name = "hsic",
+ .num_lanes = ARRAY_SIZE(tegra210_hsic_lanes),
+ .lanes = tegra210_hsic_lanes,
+ .ops = &tegra210_hsic_ops,
+};
+
+static const char *tegra210_pcie_functions[] = {
+ "pcie-x1",
+ "usb3-ss",
+ "sata",
+ "pcie-x4",
+};
+
+static const struct tegra_xusb_lane_soc tegra210_pcie_lanes[] = {
+ TEGRA210_LANE("pcie-0", 0x028, 12, 0x3, pcie),
+ TEGRA210_LANE("pcie-1", 0x028, 14, 0x3, pcie),
+ TEGRA210_LANE("pcie-2", 0x028, 16, 0x3, pcie),
+ TEGRA210_LANE("pcie-3", 0x028, 18, 0x3, pcie),
+ TEGRA210_LANE("pcie-4", 0x028, 20, 0x3, pcie),
+ TEGRA210_LANE("pcie-5", 0x028, 22, 0x3, pcie),
+ TEGRA210_LANE("pcie-6", 0x028, 24, 0x3, pcie),
+};
+
+static struct tegra_xusb_lane *
+tegra210_pcie_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+ unsigned int index)
+{
+ struct tegra_xusb_pcie_lane *pcie;
+ int err;
+
+ pcie = kzalloc(sizeof(*pcie), GFP_KERNEL);
+ if (!pcie)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&pcie->base.list);
+ pcie->base.soc = &pad->soc->lanes[index];
+ pcie->base.index = index;
+ pcie->base.pad = pad;
+ pcie->base.np = np;
+
+ err = tegra_xusb_lane_parse_dt(&pcie->base, np);
+ if (err < 0) {
+ kfree(pcie);
+ return ERR_PTR(err);
+ }
+
+ return &pcie->base;
+}
+
+static void tegra210_pcie_lane_remove(struct tegra_xusb_lane *lane)
+{
+ struct tegra_xusb_pcie_lane *pcie = to_pcie_lane(lane);
+
+ kfree(pcie);
+}
+
+static const struct tegra_xusb_lane_ops tegra210_pcie_lane_ops = {
+ .probe = tegra210_pcie_lane_probe,
+ .remove = tegra210_pcie_lane_remove,
+};
+
+static int tegra210_pcie_phy_init(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+ return tegra210_xusb_padctl_enable(lane->pad->padctl);
+}
+
+static int tegra210_pcie_phy_exit(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+ return tegra210_xusb_padctl_disable(lane->pad->padctl);
+}
+
+static int tegra210_pcie_phy_power_on(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ u32 value;
+ int err;
+
+ mutex_lock(&padctl->lock);
+
+ err = tegra210_pex_uphy_enable(padctl);
+ if (err < 0)
+ goto unlock;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX);
+ value |= XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->index);
+ padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX);
+
+unlock:
+ mutex_unlock(&padctl->lock);
+ return err;
+}
+
+static int tegra210_pcie_phy_power_off(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ u32 value;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX);
+ value &= ~XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->index);
+ padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX);
+
+ tegra210_pex_uphy_disable(padctl);
+
+ return 0;
+}
+
+static const struct phy_ops tegra210_pcie_phy_ops = {
+ .init = tegra210_pcie_phy_init,
+ .exit = tegra210_pcie_phy_exit,
+ .power_on = tegra210_pcie_phy_power_on,
+ .power_off = tegra210_pcie_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra210_pcie_pad_probe(struct tegra_xusb_padctl *padctl,
+ const struct tegra_xusb_pad_soc *soc,
+ struct device_node *np)
+{
+ struct tegra_xusb_pcie_pad *pcie;
+ struct tegra_xusb_pad *pad;
+ int err;
+
+ pcie = kzalloc(sizeof(*pcie), GFP_KERNEL);
+ if (!pcie)
+ return ERR_PTR(-ENOMEM);
+
+ pad = &pcie->base;
+ pad->ops = &tegra210_pcie_lane_ops;
+ pad->soc = soc;
+
+ err = tegra_xusb_pad_init(pad, padctl, np);
+ if (err < 0) {
+ kfree(pcie);
+ goto out;
+ }
+
+ pcie->pll = devm_clk_get(&pad->dev, "pll");
+ if (IS_ERR(pcie->pll)) {
+ err = PTR_ERR(pcie->pll);
+ dev_err(&pad->dev, "failed to get PLL: %d\n", err);
+ goto unregister;
+ }
+
+ pcie->rst = devm_reset_control_get(&pad->dev, "phy");
+ if (IS_ERR(pcie->rst)) {
+ err = PTR_ERR(pcie->rst);
+ dev_err(&pad->dev, "failed to get PCIe pad reset: %d\n", err);
+ goto unregister;
+ }
+
+ err = tegra_xusb_pad_register(pad, &tegra210_pcie_phy_ops);
+ if (err < 0)
+ goto unregister;
+
+ dev_set_drvdata(&pad->dev, pad);
+
+ return pad;
+
+unregister:
+ device_unregister(&pad->dev);
+out:
+ return ERR_PTR(err);
+}
+
+static void tegra210_pcie_pad_remove(struct tegra_xusb_pad *pad)
+{
+ struct tegra_xusb_pcie_pad *pcie = to_pcie_pad(pad);
+
+ kfree(pcie);
+}
+
+static const struct tegra_xusb_pad_ops tegra210_pcie_ops = {
+ .probe = tegra210_pcie_pad_probe,
+ .remove = tegra210_pcie_pad_remove,
+};
+
+static const struct tegra_xusb_pad_soc tegra210_pcie_pad = {
+ .name = "pcie",
+ .num_lanes = ARRAY_SIZE(tegra210_pcie_lanes),
+ .lanes = tegra210_pcie_lanes,
+ .ops = &tegra210_pcie_ops,
+};
+
+static const struct tegra_xusb_lane_soc tegra210_sata_lanes[] = {
+ TEGRA210_LANE("sata-0", 0x028, 30, 0x3, pcie),
+};
+
+static struct tegra_xusb_lane *
+tegra210_sata_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+ unsigned int index)
+{
+ struct tegra_xusb_sata_lane *sata;
+ int err;
+
+ sata = kzalloc(sizeof(*sata), GFP_KERNEL);
+ if (!sata)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&sata->base.list);
+ sata->base.soc = &pad->soc->lanes[index];
+ sata->base.index = index;
+ sata->base.pad = pad;
+ sata->base.np = np;
+
+ err = tegra_xusb_lane_parse_dt(&sata->base, np);
+ if (err < 0) {
+ kfree(sata);
+ return ERR_PTR(err);
+ }
+
+ return &sata->base;
+}
+
+static void tegra210_sata_lane_remove(struct tegra_xusb_lane *lane)
+{
+ struct tegra_xusb_sata_lane *sata = to_sata_lane(lane);
+
+ kfree(sata);
+}
+
+static const struct tegra_xusb_lane_ops tegra210_sata_lane_ops = {
+ .probe = tegra210_sata_lane_probe,
+ .remove = tegra210_sata_lane_remove,
+};
+
+static int tegra210_sata_phy_init(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+ return tegra210_xusb_padctl_enable(lane->pad->padctl);
+}
+
+static int tegra210_sata_phy_exit(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+ return tegra210_xusb_padctl_disable(lane->pad->padctl);
+}
+
+static int tegra210_sata_phy_power_on(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ u32 value;
+ int err;
+
+ mutex_lock(&padctl->lock);
+
+ err = tegra210_sata_uphy_enable(padctl, false);
+ if (err < 0)
+ goto unlock;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX);
+ value |= XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(lane->index);
+ padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX);
+
+unlock:
+ mutex_unlock(&padctl->lock);
+ return err;
+}
+
+static int tegra210_sata_phy_power_off(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ u32 value;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX);
+ value &= ~XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(lane->index);
+ padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX);
+
+ tegra210_sata_uphy_disable(lane->pad->padctl);
+
+ return 0;
+}
+
+static const struct phy_ops tegra210_sata_phy_ops = {
+ .init = tegra210_sata_phy_init,
+ .exit = tegra210_sata_phy_exit,
+ .power_on = tegra210_sata_phy_power_on,
+ .power_off = tegra210_sata_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra210_sata_pad_probe(struct tegra_xusb_padctl *padctl,
+ const struct tegra_xusb_pad_soc *soc,
+ struct device_node *np)
+{
+ struct tegra_xusb_sata_pad *sata;
+ struct tegra_xusb_pad *pad;
+ int err;
+
+ sata = kzalloc(sizeof(*sata), GFP_KERNEL);
+ if (!sata)
+ return ERR_PTR(-ENOMEM);
+
+ pad = &sata->base;
+ pad->ops = &tegra210_sata_lane_ops;
+ pad->soc = soc;
+
+ err = tegra_xusb_pad_init(pad, padctl, np);
+ if (err < 0) {
+ kfree(sata);
+ goto out;
+ }
+
+ sata->rst = devm_reset_control_get(&pad->dev, "phy");
+ if (IS_ERR(sata->rst)) {
+ err = PTR_ERR(sata->rst);
+ dev_err(&pad->dev, "failed to get SATA pad reset: %d\n", err);
+ goto unregister;
+ }
+
+ err = tegra_xusb_pad_register(pad, &tegra210_sata_phy_ops);
+ if (err < 0)
+ goto unregister;
+
+ dev_set_drvdata(&pad->dev, pad);
+
+ return pad;
+
+unregister:
+ device_unregister(&pad->dev);
+out:
+ return ERR_PTR(err);
+}
+
+static void tegra210_sata_pad_remove(struct tegra_xusb_pad *pad)
+{
+ struct tegra_xusb_sata_pad *sata = to_sata_pad(pad);
+
+ kfree(sata);
+}
+
+static const struct tegra_xusb_pad_ops tegra210_sata_ops = {
+ .probe = tegra210_sata_pad_probe,
+ .remove = tegra210_sata_pad_remove,
+};
+
+static const struct tegra_xusb_pad_soc tegra210_sata_pad = {
+ .name = "sata",
+ .num_lanes = ARRAY_SIZE(tegra210_sata_lanes),
+ .lanes = tegra210_sata_lanes,
+ .ops = &tegra210_sata_ops,
+};
+
+static const struct tegra_xusb_pad_soc * const tegra210_pads[] = {
+ &tegra210_usb2_pad,
+ &tegra210_hsic_pad,
+ &tegra210_pcie_pad,
+ &tegra210_sata_pad,
+};
+
+static int tegra210_usb2_port_enable(struct tegra_xusb_port *port)
+{
+ return 0;
+}
+
+static void tegra210_usb2_port_disable(struct tegra_xusb_port *port)
+{
+}
+
+static struct tegra_xusb_lane *
+tegra210_usb2_port_map(struct tegra_xusb_port *port)
+{
+ return tegra_xusb_find_lane(port->padctl, "usb2", port->index);
+}
+
+static const struct tegra_xusb_port_ops tegra210_usb2_port_ops = {
+ .enable = tegra210_usb2_port_enable,
+ .disable = tegra210_usb2_port_disable,
+ .map = tegra210_usb2_port_map,
+};
+
+static int tegra210_hsic_port_enable(struct tegra_xusb_port *port)
+{
+ return 0;
+}
+
+static void tegra210_hsic_port_disable(struct tegra_xusb_port *port)
+{
+}
+
+static struct tegra_xusb_lane *
+tegra210_hsic_port_map(struct tegra_xusb_port *port)
+{
+ return tegra_xusb_find_lane(port->padctl, "hsic", port->index);
+}
+
+static const struct tegra_xusb_port_ops tegra210_hsic_port_ops = {
+ .enable = tegra210_hsic_port_enable,
+ .disable = tegra210_hsic_port_disable,
+ .map = tegra210_hsic_port_map,
+};
+
+static int tegra210_usb3_port_enable(struct tegra_xusb_port *port)
+{
+ struct tegra_xusb_usb3_port *usb3 = to_usb3_port(port);
+ struct tegra_xusb_padctl *padctl = port->padctl;
+ struct tegra_xusb_lane *lane = usb3->base.lane;
+ unsigned int index = port->index;
+ u32 value;
+ int err;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP);
+
+ if (!usb3->internal)
+ value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index);
+ else
+ value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index);
+
+ value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(index);
+ value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(index, usb3->port);
+ padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP);
+
+ /*
+ * TODO: move this code into the PCIe/SATA PHY ->power_on() callbacks
+ * and conditionalize based on mux function? This seems to work, but
+ * might not be the exact proper sequence.
+ */
+ err = regulator_enable(usb3->supply);
+ if (err < 0)
+ return err;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_USB3_PADX_ECTL1(index));
+ value &= ~(XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_MASK <<
+ XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_SHIFT);
+ value |= XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_VAL <<
+ XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_SHIFT;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_USB3_PADX_ECTL1(index));
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_USB3_PADX_ECTL2(index));
+ value &= ~(XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_MASK <<
+ XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_SHIFT);
+ value |= XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_VAL <<
+ XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_SHIFT;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_USB3_PADX_ECTL2(index));
+
+ padctl_writel(padctl, XUSB_PADCTL_UPHY_USB3_PAD_ECTL3_RX_DFE_VAL,
+ XUSB_PADCTL_UPHY_USB3_PADX_ECTL3(index));
+
+ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_USB3_PADX_ECTL4(index));
+ value &= ~(XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_MASK <<
+ XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_SHIFT);
+ value |= XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_VAL <<
+ XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_SHIFT;
+ padctl_writel(padctl, value, XUSB_PADCTL_UPHY_USB3_PADX_ECTL4(index));
+
+ padctl_writel(padctl, XUSB_PADCTL_UPHY_USB3_PAD_ECTL6_RX_EQ_CTRL_H_VAL,
+ XUSB_PADCTL_UPHY_USB3_PADX_ECTL6(index));
+
+ if (lane->pad == padctl->sata)
+ err = tegra210_sata_uphy_enable(padctl, true);
+ else
+ err = tegra210_pex_uphy_enable(padctl);
+
+ if (err) {
+ dev_err(&port->dev, "%s: failed to enable UPHY: %d\n",
+ __func__, err);
+ return err;
+ }
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+ value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_VCORE_DOWN(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+ value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+ value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+ return 0;
+}
+
+static void tegra210_usb3_port_disable(struct tegra_xusb_port *port)
+{
+ struct tegra_xusb_usb3_port *usb3 = to_usb3_port(port);
+ struct tegra_xusb_padctl *padctl = port->padctl;
+ struct tegra_xusb_lane *lane = port->lane;
+ unsigned int index = port->index;
+ u32 value;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+ value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+ value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+ usleep_range(250, 350);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+ value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_VCORE_DOWN(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+ if (lane->pad == padctl->sata)
+ tegra210_sata_uphy_disable(padctl);
+ else
+ tegra210_pex_uphy_disable(padctl);
+
+ regulator_disable(usb3->supply);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP);
+ value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(index);
+ value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(index, 0x7);
+ padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP);
+}
+
+static const struct tegra_xusb_lane_map tegra210_usb3_map[] = {
+ { 0, "pcie", 6 },
+ { 1, "pcie", 5 },
+ { 2, "pcie", 0 },
+ { 2, "pcie", 3 },
+ { 3, "pcie", 4 },
+ { 3, "pcie", 4 },
+ { 0, NULL, 0 }
+};
+
+static struct tegra_xusb_lane *
+tegra210_usb3_port_map(struct tegra_xusb_port *port)
+{
+ return tegra_xusb_port_find_lane(port, tegra210_usb3_map, "usb3-ss");
+}
+
+static const struct tegra_xusb_port_ops tegra210_usb3_port_ops = {
+ .enable = tegra210_usb3_port_enable,
+ .disable = tegra210_usb3_port_disable,
+ .map = tegra210_usb3_port_map,
+};
+
+static int
+tegra210_xusb_read_fuse_calibration(struct tegra210_xusb_fuse_calibration *fuse)
+{
+ unsigned int i;
+ u32 value;
+ int err;
+
+ err = tegra_fuse_readl(TEGRA_FUSE_SKU_CALIB_0, &value);
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < ARRAY_SIZE(fuse->hs_curr_level); i++) {
+ fuse->hs_curr_level[i] =
+ (value >> FUSE_SKU_CALIB_HS_CURR_LEVEL_PADX_SHIFT(i)) &
+ FUSE_SKU_CALIB_HS_CURR_LEVEL_PAD_MASK;
+ }
+
+ fuse->hs_term_range_adj =
+ (value >> FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_SHIFT) &
+ FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_MASK;
+
+ err = tegra_fuse_readl(TEGRA_FUSE_USB_CALIB_EXT_0, &value);
+ if (err < 0)
+ return err;
+
+ fuse->rpd_ctrl =
+ (value >> FUSE_USB_CALIB_EXT_RPD_CTRL_SHIFT) &
+ FUSE_USB_CALIB_EXT_RPD_CTRL_MASK;
+
+ return 0;
+}
+
+static struct tegra_xusb_padctl *
+tegra210_xusb_padctl_probe(struct device *dev,
+ const struct tegra_xusb_padctl_soc *soc)
+{
+ struct tegra210_xusb_padctl *padctl;
+ int err;
+
+ padctl = devm_kzalloc(dev, sizeof(*padctl), GFP_KERNEL);
+ if (!padctl)
+ return ERR_PTR(-ENOMEM);
+
+ padctl->base.dev = dev;
+ padctl->base.soc = soc;
+
+ err = tegra210_xusb_read_fuse_calibration(&padctl->fuse);
+ if (err < 0)
+ return ERR_PTR(err);
+
+ return &padctl->base;
+}
+
+static void tegra210_xusb_padctl_remove(struct tegra_xusb_padctl *padctl)
+{
+}
+
+static const struct tegra_xusb_padctl_ops tegra210_xusb_padctl_ops = {
+ .probe = tegra210_xusb_padctl_probe,
+ .remove = tegra210_xusb_padctl_remove,
+ .usb3_set_lfps_detect = tegra210_usb3_set_lfps_detect,
+ .hsic_set_idle = tegra210_hsic_set_idle,
+};
+
+const struct tegra_xusb_padctl_soc tegra210_xusb_padctl_soc = {
+ .num_pads = ARRAY_SIZE(tegra210_pads),
+ .pads = tegra210_pads,
+ .ports = {
+ .usb2 = {
+ .ops = &tegra210_usb2_port_ops,
+ .count = 4,
+ },
+ .hsic = {
+ .ops = &tegra210_hsic_port_ops,
+ .count = 1,
+ },
+ .usb3 = {
+ .ops = &tegra210_usb3_port_ops,
+ .count = 4,
+ },
+ },
+ .ops = &tegra210_xusb_padctl_ops,
+};
+EXPORT_SYMBOL_GPL(tegra210_xusb_padctl_soc);
+
+MODULE_AUTHOR("Andrew Bresticker <abrestic@chromium.org>");
+MODULE_DESCRIPTION("NVIDIA Tegra 210 XUSB Pad Controller driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c
new file mode 100644
index 000000000..39c01ef57
--- /dev/null
+++ b/drivers/phy/tegra/xusb.c
@@ -0,0 +1,1007 @@
+/*
+ * Copyright (c) 2014-2015, NVIDIA CORPORATION. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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/delay.h>
+#include <linux/io.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#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>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <soc/tegra/fuse.h>
+
+#include "xusb.h"
+
+static struct phy *tegra_xusb_pad_of_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct tegra_xusb_pad *pad = dev_get_drvdata(dev);
+ struct phy *phy = NULL;
+ unsigned int i;
+
+ if (args->args_count != 0)
+ return ERR_PTR(-EINVAL);
+
+ for (i = 0; i < pad->soc->num_lanes; i++) {
+ if (!pad->lanes[i])
+ continue;
+
+ if (pad->lanes[i]->dev.of_node == args->np) {
+ phy = pad->lanes[i];
+ break;
+ }
+ }
+
+ if (phy == NULL)
+ phy = ERR_PTR(-ENODEV);
+
+ return phy;
+}
+
+static const struct of_device_id tegra_xusb_padctl_of_match[] = {
+#if defined(CONFIG_ARCH_TEGRA_124_SOC) || defined(CONFIG_ARCH_TEGRA_132_SOC)
+ {
+ .compatible = "nvidia,tegra124-xusb-padctl",
+ .data = &tegra124_xusb_padctl_soc,
+ },
+#endif
+#if defined(CONFIG_ARCH_TEGRA_210_SOC)
+ {
+ .compatible = "nvidia,tegra210-xusb-padctl",
+ .data = &tegra210_xusb_padctl_soc,
+ },
+#endif
+ { }
+};
+MODULE_DEVICE_TABLE(of, tegra_xusb_padctl_of_match);
+
+static struct device_node *
+tegra_xusb_find_pad_node(struct tegra_xusb_padctl *padctl, const char *name)
+{
+ struct device_node *pads, *np;
+
+ pads = of_get_child_by_name(padctl->dev->of_node, "pads");
+ if (!pads)
+ return NULL;
+
+ np = of_get_child_by_name(pads, name);
+ of_node_put(pads);
+
+ return np;
+}
+
+static struct device_node *
+tegra_xusb_pad_find_phy_node(struct tegra_xusb_pad *pad, unsigned int index)
+{
+ struct device_node *np, *lanes;
+
+ lanes = of_get_child_by_name(pad->dev.of_node, "lanes");
+ if (!lanes)
+ return NULL;
+
+ np = of_get_child_by_name(lanes, pad->soc->lanes[index].name);
+ of_node_put(lanes);
+
+ return np;
+}
+
+int tegra_xusb_lane_parse_dt(struct tegra_xusb_lane *lane,
+ struct device_node *np)
+{
+ struct device *dev = &lane->pad->dev;
+ const char *function;
+ int err;
+
+ err = of_property_read_string(np, "nvidia,function", &function);
+ if (err < 0)
+ return err;
+
+ err = match_string(lane->soc->funcs, lane->soc->num_funcs, function);
+ if (err < 0) {
+ dev_err(dev, "invalid function \"%s\" for lane \"%s\"\n",
+ function, np->name);
+ return err;
+ }
+
+ lane->function = err;
+
+ return 0;
+}
+
+static void tegra_xusb_lane_destroy(struct phy *phy)
+{
+ if (phy) {
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+ lane->pad->ops->remove(lane);
+ phy_destroy(phy);
+ }
+}
+
+static void tegra_xusb_pad_release(struct device *dev)
+{
+ struct tegra_xusb_pad *pad = to_tegra_xusb_pad(dev);
+
+ pad->soc->ops->remove(pad);
+}
+
+static struct device_type tegra_xusb_pad_type = {
+ .release = tegra_xusb_pad_release,
+};
+
+int tegra_xusb_pad_init(struct tegra_xusb_pad *pad,
+ struct tegra_xusb_padctl *padctl,
+ struct device_node *np)
+{
+ int err;
+
+ device_initialize(&pad->dev);
+ INIT_LIST_HEAD(&pad->list);
+ pad->dev.parent = padctl->dev;
+ pad->dev.type = &tegra_xusb_pad_type;
+ pad->dev.of_node = np;
+ pad->padctl = padctl;
+
+ err = dev_set_name(&pad->dev, "%s", pad->soc->name);
+ if (err < 0)
+ goto unregister;
+
+ err = device_add(&pad->dev);
+ if (err < 0)
+ goto unregister;
+
+ return 0;
+
+unregister:
+ device_unregister(&pad->dev);
+ return err;
+}
+
+int tegra_xusb_pad_register(struct tegra_xusb_pad *pad,
+ const struct phy_ops *ops)
+{
+ struct device_node *children;
+ struct phy *lane;
+ unsigned int i;
+ int err;
+
+ children = of_get_child_by_name(pad->dev.of_node, "lanes");
+ if (!children)
+ return -ENODEV;
+
+ pad->lanes = devm_kcalloc(&pad->dev, pad->soc->num_lanes, sizeof(lane),
+ GFP_KERNEL);
+ if (!pad->lanes) {
+ of_node_put(children);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < pad->soc->num_lanes; i++) {
+ struct device_node *np = tegra_xusb_pad_find_phy_node(pad, i);
+ struct tegra_xusb_lane *lane;
+
+ /* skip disabled lanes */
+ if (!np || !of_device_is_available(np)) {
+ of_node_put(np);
+ continue;
+ }
+
+ pad->lanes[i] = phy_create(&pad->dev, np, ops);
+ if (IS_ERR(pad->lanes[i])) {
+ err = PTR_ERR(pad->lanes[i]);
+ of_node_put(np);
+ goto remove;
+ }
+
+ lane = pad->ops->probe(pad, np, i);
+ if (IS_ERR(lane)) {
+ phy_destroy(pad->lanes[i]);
+ err = PTR_ERR(lane);
+ goto remove;
+ }
+
+ list_add_tail(&lane->list, &pad->padctl->lanes);
+ phy_set_drvdata(pad->lanes[i], lane);
+ }
+
+ pad->provider = of_phy_provider_register_full(&pad->dev, children,
+ tegra_xusb_pad_of_xlate);
+ if (IS_ERR(pad->provider)) {
+ err = PTR_ERR(pad->provider);
+ goto remove;
+ }
+
+ return 0;
+
+remove:
+ while (i--)
+ tegra_xusb_lane_destroy(pad->lanes[i]);
+
+ of_node_put(children);
+
+ return err;
+}
+
+void tegra_xusb_pad_unregister(struct tegra_xusb_pad *pad)
+{
+ unsigned int i = pad->soc->num_lanes;
+
+ of_phy_provider_unregister(pad->provider);
+
+ while (i--)
+ tegra_xusb_lane_destroy(pad->lanes[i]);
+
+ device_unregister(&pad->dev);
+}
+
+static struct tegra_xusb_pad *
+tegra_xusb_pad_create(struct tegra_xusb_padctl *padctl,
+ const struct tegra_xusb_pad_soc *soc)
+{
+ struct tegra_xusb_pad *pad;
+ struct device_node *np;
+ int err;
+
+ np = tegra_xusb_find_pad_node(padctl, soc->name);
+ if (!np || !of_device_is_available(np))
+ return NULL;
+
+ pad = soc->ops->probe(padctl, soc, np);
+ if (IS_ERR(pad)) {
+ err = PTR_ERR(pad);
+ dev_err(padctl->dev, "failed to create pad %s: %d\n",
+ soc->name, err);
+ return ERR_PTR(err);
+ }
+
+ /* XXX move this into ->probe() to avoid string comparison */
+ if (strcmp(soc->name, "pcie") == 0)
+ padctl->pcie = pad;
+
+ if (strcmp(soc->name, "sata") == 0)
+ padctl->sata = pad;
+
+ if (strcmp(soc->name, "usb2") == 0)
+ padctl->usb2 = pad;
+
+ if (strcmp(soc->name, "ulpi") == 0)
+ padctl->ulpi = pad;
+
+ if (strcmp(soc->name, "hsic") == 0)
+ padctl->hsic = pad;
+
+ return pad;
+}
+
+static void __tegra_xusb_remove_pads(struct tegra_xusb_padctl *padctl)
+{
+ struct tegra_xusb_pad *pad, *tmp;
+
+ list_for_each_entry_safe_reverse(pad, tmp, &padctl->pads, list) {
+ list_del(&pad->list);
+ tegra_xusb_pad_unregister(pad);
+ }
+}
+
+static void tegra_xusb_remove_pads(struct tegra_xusb_padctl *padctl)
+{
+ mutex_lock(&padctl->lock);
+ __tegra_xusb_remove_pads(padctl);
+ mutex_unlock(&padctl->lock);
+}
+
+static void tegra_xusb_lane_program(struct tegra_xusb_lane *lane)
+{
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ const struct tegra_xusb_lane_soc *soc = lane->soc;
+ u32 value;
+
+ /* choose function */
+ value = padctl_readl(padctl, soc->offset);
+ value &= ~(soc->mask << soc->shift);
+ value |= lane->function << soc->shift;
+ padctl_writel(padctl, value, soc->offset);
+}
+
+static void tegra_xusb_pad_program(struct tegra_xusb_pad *pad)
+{
+ unsigned int i;
+
+ for (i = 0; i < pad->soc->num_lanes; i++) {
+ struct tegra_xusb_lane *lane;
+
+ if (pad->lanes[i]) {
+ lane = phy_get_drvdata(pad->lanes[i]);
+ tegra_xusb_lane_program(lane);
+ }
+ }
+}
+
+static int tegra_xusb_setup_pads(struct tegra_xusb_padctl *padctl)
+{
+ struct tegra_xusb_pad *pad;
+ unsigned int i;
+
+ mutex_lock(&padctl->lock);
+
+ for (i = 0; i < padctl->soc->num_pads; i++) {
+ const struct tegra_xusb_pad_soc *soc = padctl->soc->pads[i];
+ int err;
+
+ pad = tegra_xusb_pad_create(padctl, soc);
+ if (IS_ERR(pad)) {
+ err = PTR_ERR(pad);
+ dev_err(padctl->dev, "failed to create pad %s: %d\n",
+ soc->name, err);
+ __tegra_xusb_remove_pads(padctl);
+ mutex_unlock(&padctl->lock);
+ return err;
+ }
+
+ if (!pad)
+ continue;
+
+ list_add_tail(&pad->list, &padctl->pads);
+ }
+
+ list_for_each_entry(pad, &padctl->pads, list)
+ tegra_xusb_pad_program(pad);
+
+ mutex_unlock(&padctl->lock);
+ return 0;
+}
+
+static bool tegra_xusb_lane_check(struct tegra_xusb_lane *lane,
+ const char *function)
+{
+ const char *func = lane->soc->funcs[lane->function];
+
+ return strcmp(function, func) == 0;
+}
+
+struct tegra_xusb_lane *tegra_xusb_find_lane(struct tegra_xusb_padctl *padctl,
+ const char *type,
+ unsigned int index)
+{
+ struct tegra_xusb_lane *lane, *hit = ERR_PTR(-ENODEV);
+ char *name;
+
+ name = kasprintf(GFP_KERNEL, "%s-%u", type, index);
+ if (!name)
+ return ERR_PTR(-ENOMEM);
+
+ list_for_each_entry(lane, &padctl->lanes, list) {
+ if (strcmp(lane->soc->name, name) == 0) {
+ hit = lane;
+ break;
+ }
+ }
+
+ kfree(name);
+ return hit;
+}
+
+struct tegra_xusb_lane *
+tegra_xusb_port_find_lane(struct tegra_xusb_port *port,
+ const struct tegra_xusb_lane_map *map,
+ const char *function)
+{
+ struct tegra_xusb_lane *lane, *match = ERR_PTR(-ENODEV);
+
+ for (; map->type; map++) {
+ if (port->index != map->port)
+ continue;
+
+ lane = tegra_xusb_find_lane(port->padctl, map->type,
+ map->index);
+ if (IS_ERR(lane))
+ continue;
+
+ if (!tegra_xusb_lane_check(lane, function))
+ continue;
+
+ if (!IS_ERR(match))
+ dev_err(&port->dev, "conflicting match: %s-%u / %s\n",
+ map->type, map->index, match->soc->name);
+ else
+ match = lane;
+ }
+
+ return match;
+}
+
+static struct device_node *
+tegra_xusb_find_port_node(struct tegra_xusb_padctl *padctl, const char *type,
+ unsigned int index)
+{
+ struct device_node *ports, *np;
+ char *name;
+
+ ports = of_get_child_by_name(padctl->dev->of_node, "ports");
+ if (!ports)
+ return NULL;
+
+ name = kasprintf(GFP_KERNEL, "%s-%u", type, index);
+ if (!name) {
+ of_node_put(ports);
+ return ERR_PTR(-ENOMEM);
+ }
+ np = of_get_child_by_name(ports, name);
+ kfree(name);
+ of_node_put(ports);
+
+ return np;
+}
+
+struct tegra_xusb_port *
+tegra_xusb_find_port(struct tegra_xusb_padctl *padctl, const char *type,
+ unsigned int index)
+{
+ struct tegra_xusb_port *port;
+ struct device_node *np;
+
+ np = tegra_xusb_find_port_node(padctl, type, index);
+ if (!np)
+ return NULL;
+
+ list_for_each_entry(port, &padctl->ports, list) {
+ if (np == port->dev.of_node) {
+ of_node_put(np);
+ return port;
+ }
+ }
+
+ of_node_put(np);
+
+ return NULL;
+}
+
+struct tegra_xusb_usb2_port *
+tegra_xusb_find_usb2_port(struct tegra_xusb_padctl *padctl, unsigned int index)
+{
+ struct tegra_xusb_port *port;
+
+ port = tegra_xusb_find_port(padctl, "usb2", index);
+ if (port)
+ return to_usb2_port(port);
+
+ return NULL;
+}
+
+struct tegra_xusb_usb3_port *
+tegra_xusb_find_usb3_port(struct tegra_xusb_padctl *padctl, unsigned int index)
+{
+ struct tegra_xusb_port *port;
+
+ port = tegra_xusb_find_port(padctl, "usb3", index);
+ if (port)
+ return to_usb3_port(port);
+
+ return NULL;
+}
+
+static void tegra_xusb_port_release(struct device *dev)
+{
+}
+
+static struct device_type tegra_xusb_port_type = {
+ .release = tegra_xusb_port_release,
+};
+
+static int tegra_xusb_port_init(struct tegra_xusb_port *port,
+ struct tegra_xusb_padctl *padctl,
+ struct device_node *np,
+ const char *name,
+ unsigned int index)
+{
+ int err;
+
+ INIT_LIST_HEAD(&port->list);
+ port->padctl = padctl;
+ port->index = index;
+
+ device_initialize(&port->dev);
+ port->dev.type = &tegra_xusb_port_type;
+ port->dev.of_node = of_node_get(np);
+ port->dev.parent = padctl->dev;
+
+ err = dev_set_name(&port->dev, "%s-%u", name, index);
+ if (err < 0)
+ goto unregister;
+
+ err = device_add(&port->dev);
+ if (err < 0)
+ goto unregister;
+
+ return 0;
+
+unregister:
+ device_unregister(&port->dev);
+ return err;
+}
+
+static void tegra_xusb_port_unregister(struct tegra_xusb_port *port)
+{
+ device_unregister(&port->dev);
+}
+
+static int tegra_xusb_usb2_port_parse_dt(struct tegra_xusb_usb2_port *usb2)
+{
+ struct tegra_xusb_port *port = &usb2->base;
+ struct device_node *np = port->dev.of_node;
+
+ usb2->internal = of_property_read_bool(np, "nvidia,internal");
+
+ usb2->supply = devm_regulator_get(&port->dev, "vbus");
+ return PTR_ERR_OR_ZERO(usb2->supply);
+}
+
+static int tegra_xusb_add_usb2_port(struct tegra_xusb_padctl *padctl,
+ unsigned int index)
+{
+ struct tegra_xusb_usb2_port *usb2;
+ struct device_node *np;
+ int err = 0;
+
+ /*
+ * USB2 ports don't require additional properties, but if the port is
+ * marked as disabled there is no reason to register it.
+ */
+ np = tegra_xusb_find_port_node(padctl, "usb2", index);
+ if (!np || !of_device_is_available(np))
+ goto out;
+
+ usb2 = devm_kzalloc(padctl->dev, sizeof(*usb2), GFP_KERNEL);
+ if (!usb2) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ err = tegra_xusb_port_init(&usb2->base, padctl, np, "usb2", index);
+ if (err < 0)
+ goto out;
+
+ usb2->base.ops = padctl->soc->ports.usb2.ops;
+
+ usb2->base.lane = usb2->base.ops->map(&usb2->base);
+ if (IS_ERR(usb2->base.lane)) {
+ err = PTR_ERR(usb2->base.lane);
+ goto out;
+ }
+
+ err = tegra_xusb_usb2_port_parse_dt(usb2);
+ if (err < 0) {
+ tegra_xusb_port_unregister(&usb2->base);
+ goto out;
+ }
+
+ list_add_tail(&usb2->base.list, &padctl->ports);
+
+out:
+ of_node_put(np);
+ return err;
+}
+
+static int tegra_xusb_ulpi_port_parse_dt(struct tegra_xusb_ulpi_port *ulpi)
+{
+ struct tegra_xusb_port *port = &ulpi->base;
+ struct device_node *np = port->dev.of_node;
+
+ ulpi->internal = of_property_read_bool(np, "nvidia,internal");
+
+ return 0;
+}
+
+static int tegra_xusb_add_ulpi_port(struct tegra_xusb_padctl *padctl,
+ unsigned int index)
+{
+ struct tegra_xusb_ulpi_port *ulpi;
+ struct device_node *np;
+ int err = 0;
+
+ np = tegra_xusb_find_port_node(padctl, "ulpi", index);
+ if (!np || !of_device_is_available(np))
+ goto out;
+
+ ulpi = devm_kzalloc(padctl->dev, sizeof(*ulpi), GFP_KERNEL);
+ if (!ulpi) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ err = tegra_xusb_port_init(&ulpi->base, padctl, np, "ulpi", index);
+ if (err < 0)
+ goto out;
+
+ ulpi->base.ops = padctl->soc->ports.ulpi.ops;
+
+ ulpi->base.lane = ulpi->base.ops->map(&ulpi->base);
+ if (IS_ERR(ulpi->base.lane)) {
+ err = PTR_ERR(ulpi->base.lane);
+ goto out;
+ }
+
+ err = tegra_xusb_ulpi_port_parse_dt(ulpi);
+ if (err < 0) {
+ tegra_xusb_port_unregister(&ulpi->base);
+ goto out;
+ }
+
+ list_add_tail(&ulpi->base.list, &padctl->ports);
+
+out:
+ of_node_put(np);
+ return err;
+}
+
+static int tegra_xusb_hsic_port_parse_dt(struct tegra_xusb_hsic_port *hsic)
+{
+ /* XXX */
+ return 0;
+}
+
+static int tegra_xusb_add_hsic_port(struct tegra_xusb_padctl *padctl,
+ unsigned int index)
+{
+ struct tegra_xusb_hsic_port *hsic;
+ struct device_node *np;
+ int err = 0;
+
+ np = tegra_xusb_find_port_node(padctl, "hsic", index);
+ if (!np || !of_device_is_available(np))
+ goto out;
+
+ hsic = devm_kzalloc(padctl->dev, sizeof(*hsic), GFP_KERNEL);
+ if (!hsic) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ err = tegra_xusb_port_init(&hsic->base, padctl, np, "hsic", index);
+ if (err < 0)
+ goto out;
+
+ hsic->base.ops = padctl->soc->ports.hsic.ops;
+
+ hsic->base.lane = hsic->base.ops->map(&hsic->base);
+ if (IS_ERR(hsic->base.lane)) {
+ err = PTR_ERR(hsic->base.lane);
+ goto out;
+ }
+
+ err = tegra_xusb_hsic_port_parse_dt(hsic);
+ if (err < 0) {
+ tegra_xusb_port_unregister(&hsic->base);
+ goto out;
+ }
+
+ list_add_tail(&hsic->base.list, &padctl->ports);
+
+out:
+ of_node_put(np);
+ return err;
+}
+
+static int tegra_xusb_usb3_port_parse_dt(struct tegra_xusb_usb3_port *usb3)
+{
+ struct tegra_xusb_port *port = &usb3->base;
+ struct device_node *np = port->dev.of_node;
+ u32 value;
+ int err;
+
+ err = of_property_read_u32(np, "nvidia,usb2-companion", &value);
+ if (err < 0) {
+ dev_err(&port->dev, "failed to read port: %d\n", err);
+ return err;
+ }
+
+ usb3->port = value;
+
+ usb3->internal = of_property_read_bool(np, "nvidia,internal");
+
+ usb3->supply = devm_regulator_get(&port->dev, "vbus");
+ return PTR_ERR_OR_ZERO(usb3->supply);
+}
+
+static int tegra_xusb_add_usb3_port(struct tegra_xusb_padctl *padctl,
+ unsigned int index)
+{
+ struct tegra_xusb_usb3_port *usb3;
+ struct device_node *np;
+ int err = 0;
+
+ /*
+ * If there is no supplemental configuration in the device tree the
+ * port is unusable. But it is valid to configure only a single port,
+ * hence return 0 instead of an error to allow ports to be optional.
+ */
+ np = tegra_xusb_find_port_node(padctl, "usb3", index);
+ if (!np || !of_device_is_available(np))
+ goto out;
+
+ usb3 = devm_kzalloc(padctl->dev, sizeof(*usb3), GFP_KERNEL);
+ if (!usb3) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ err = tegra_xusb_port_init(&usb3->base, padctl, np, "usb3", index);
+ if (err < 0)
+ goto out;
+
+ usb3->base.ops = padctl->soc->ports.usb3.ops;
+
+ usb3->base.lane = usb3->base.ops->map(&usb3->base);
+ if (IS_ERR(usb3->base.lane)) {
+ err = PTR_ERR(usb3->base.lane);
+ goto out;
+ }
+
+ err = tegra_xusb_usb3_port_parse_dt(usb3);
+ if (err < 0) {
+ tegra_xusb_port_unregister(&usb3->base);
+ goto out;
+ }
+
+ list_add_tail(&usb3->base.list, &padctl->ports);
+
+out:
+ of_node_put(np);
+ return err;
+}
+
+static void __tegra_xusb_remove_ports(struct tegra_xusb_padctl *padctl)
+{
+ struct tegra_xusb_port *port, *tmp;
+
+ list_for_each_entry_safe_reverse(port, tmp, &padctl->ports, list) {
+ list_del(&port->list);
+ tegra_xusb_port_unregister(port);
+ }
+}
+
+static int tegra_xusb_setup_ports(struct tegra_xusb_padctl *padctl)
+{
+ struct tegra_xusb_port *port;
+ unsigned int i;
+ int err = 0;
+
+ mutex_lock(&padctl->lock);
+
+ for (i = 0; i < padctl->soc->ports.usb2.count; i++) {
+ err = tegra_xusb_add_usb2_port(padctl, i);
+ if (err < 0)
+ goto remove_ports;
+ }
+
+ for (i = 0; i < padctl->soc->ports.ulpi.count; i++) {
+ err = tegra_xusb_add_ulpi_port(padctl, i);
+ if (err < 0)
+ goto remove_ports;
+ }
+
+ for (i = 0; i < padctl->soc->ports.hsic.count; i++) {
+ err = tegra_xusb_add_hsic_port(padctl, i);
+ if (err < 0)
+ goto remove_ports;
+ }
+
+ for (i = 0; i < padctl->soc->ports.usb3.count; i++) {
+ err = tegra_xusb_add_usb3_port(padctl, i);
+ if (err < 0)
+ goto remove_ports;
+ }
+
+ list_for_each_entry(port, &padctl->ports, list) {
+ err = port->ops->enable(port);
+ if (err < 0)
+ dev_err(padctl->dev, "failed to enable port %s: %d\n",
+ dev_name(&port->dev), err);
+ }
+
+ goto unlock;
+
+remove_ports:
+ __tegra_xusb_remove_ports(padctl);
+unlock:
+ mutex_unlock(&padctl->lock);
+ return err;
+}
+
+static void tegra_xusb_remove_ports(struct tegra_xusb_padctl *padctl)
+{
+ mutex_lock(&padctl->lock);
+ __tegra_xusb_remove_ports(padctl);
+ mutex_unlock(&padctl->lock);
+}
+
+static int tegra_xusb_padctl_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ const struct tegra_xusb_padctl_soc *soc;
+ struct tegra_xusb_padctl *padctl;
+ const struct of_device_id *match;
+ struct resource *res;
+ int err;
+
+ /* for backwards compatibility with old device trees */
+ np = of_get_child_by_name(np, "pads");
+ if (!np) {
+ dev_warn(&pdev->dev, "deprecated DT, using legacy driver\n");
+ return tegra_xusb_padctl_legacy_probe(pdev);
+ }
+
+ of_node_put(np);
+
+ match = of_match_node(tegra_xusb_padctl_of_match, pdev->dev.of_node);
+ soc = match->data;
+
+ padctl = soc->ops->probe(&pdev->dev, soc);
+ if (IS_ERR(padctl))
+ return PTR_ERR(padctl);
+
+ platform_set_drvdata(pdev, padctl);
+ INIT_LIST_HEAD(&padctl->ports);
+ INIT_LIST_HEAD(&padctl->lanes);
+ INIT_LIST_HEAD(&padctl->pads);
+ mutex_init(&padctl->lock);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ padctl->regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(padctl->regs)) {
+ err = PTR_ERR(padctl->regs);
+ goto remove;
+ }
+
+ padctl->rst = devm_reset_control_get(&pdev->dev, NULL);
+ if (IS_ERR(padctl->rst)) {
+ err = PTR_ERR(padctl->rst);
+ goto remove;
+ }
+
+ err = reset_control_deassert(padctl->rst);
+ if (err < 0)
+ goto remove;
+
+ err = tegra_xusb_setup_pads(padctl);
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed to setup pads: %d\n", err);
+ goto reset;
+ }
+
+ err = tegra_xusb_setup_ports(padctl);
+ if (err) {
+ dev_err(&pdev->dev, "failed to setup XUSB ports: %d\n", err);
+ goto remove_pads;
+ }
+
+ return 0;
+
+remove_pads:
+ tegra_xusb_remove_pads(padctl);
+reset:
+ reset_control_assert(padctl->rst);
+remove:
+ platform_set_drvdata(pdev, NULL);
+ soc->ops->remove(padctl);
+ return err;
+}
+
+static int tegra_xusb_padctl_remove(struct platform_device *pdev)
+{
+ struct tegra_xusb_padctl *padctl = platform_get_drvdata(pdev);
+ int err;
+
+ tegra_xusb_remove_ports(padctl);
+ tegra_xusb_remove_pads(padctl);
+
+ err = reset_control_assert(padctl->rst);
+ if (err < 0)
+ dev_err(&pdev->dev, "failed to assert reset: %d\n", err);
+
+ padctl->soc->ops->remove(padctl);
+
+ return err;
+}
+
+static struct platform_driver tegra_xusb_padctl_driver = {
+ .driver = {
+ .name = "tegra-xusb-padctl",
+ .of_match_table = tegra_xusb_padctl_of_match,
+ },
+ .probe = tegra_xusb_padctl_probe,
+ .remove = tegra_xusb_padctl_remove,
+};
+module_platform_driver(tegra_xusb_padctl_driver);
+
+struct tegra_xusb_padctl *tegra_xusb_padctl_get(struct device *dev)
+{
+ struct tegra_xusb_padctl *padctl;
+ struct platform_device *pdev;
+ struct device_node *np;
+
+ np = of_parse_phandle(dev->of_node, "nvidia,xusb-padctl", 0);
+ if (!np)
+ return ERR_PTR(-EINVAL);
+
+ /*
+ * This is slightly ugly. A better implementation would be to keep a
+ * registry of pad controllers, but since there will almost certainly
+ * only ever be one per SoC that would be a little overkill.
+ */
+ pdev = of_find_device_by_node(np);
+ if (!pdev) {
+ of_node_put(np);
+ return ERR_PTR(-ENODEV);
+ }
+
+ of_node_put(np);
+
+ padctl = platform_get_drvdata(pdev);
+ if (!padctl) {
+ put_device(&pdev->dev);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ return padctl;
+}
+EXPORT_SYMBOL_GPL(tegra_xusb_padctl_get);
+
+void tegra_xusb_padctl_put(struct tegra_xusb_padctl *padctl)
+{
+ if (padctl)
+ put_device(padctl->dev);
+}
+EXPORT_SYMBOL_GPL(tegra_xusb_padctl_put);
+
+int tegra_xusb_padctl_usb3_save_context(struct tegra_xusb_padctl *padctl,
+ unsigned int port)
+{
+ if (padctl->soc->ops->usb3_save_context)
+ return padctl->soc->ops->usb3_save_context(padctl, port);
+
+ return -ENOSYS;
+}
+EXPORT_SYMBOL_GPL(tegra_xusb_padctl_usb3_save_context);
+
+int tegra_xusb_padctl_hsic_set_idle(struct tegra_xusb_padctl *padctl,
+ unsigned int port, bool idle)
+{
+ if (padctl->soc->ops->hsic_set_idle)
+ return padctl->soc->ops->hsic_set_idle(padctl, port, idle);
+
+ return -ENOSYS;
+}
+EXPORT_SYMBOL_GPL(tegra_xusb_padctl_hsic_set_idle);
+
+int tegra_xusb_padctl_usb3_set_lfps_detect(struct tegra_xusb_padctl *padctl,
+ unsigned int port, bool enable)
+{
+ if (padctl->soc->ops->usb3_set_lfps_detect)
+ return padctl->soc->ops->usb3_set_lfps_detect(padctl, port,
+ enable);
+
+ return -ENOSYS;
+}
+EXPORT_SYMBOL_GPL(tegra_xusb_padctl_usb3_set_lfps_detect);
+
+MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
+MODULE_DESCRIPTION("Tegra XUSB Pad Controller driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h
new file mode 100644
index 000000000..b49dbc36e
--- /dev/null
+++ b/drivers/phy/tegra/xusb.h
@@ -0,0 +1,421 @@
+/*
+ * Copyright (c) 2014-2015, NVIDIA CORPORATION. All rights reserved.
+ * Copyright (c) 2015, Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#ifndef __PHY_TEGRA_XUSB_H
+#define __PHY_TEGRA_XUSB_H
+
+#include <linux/io.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+
+/* legacy entry points for backwards-compatibility */
+int tegra_xusb_padctl_legacy_probe(struct platform_device *pdev);
+int tegra_xusb_padctl_legacy_remove(struct platform_device *pdev);
+
+struct phy;
+struct phy_provider;
+struct platform_device;
+struct regulator;
+
+/*
+ * lanes
+ */
+struct tegra_xusb_lane_soc {
+ const char *name;
+
+ unsigned int offset;
+ unsigned int shift;
+ unsigned int mask;
+
+ const char * const *funcs;
+ unsigned int num_funcs;
+};
+
+struct tegra_xusb_lane {
+ const struct tegra_xusb_lane_soc *soc;
+ struct tegra_xusb_pad *pad;
+ struct device_node *np;
+ struct list_head list;
+ unsigned int function;
+ unsigned int index;
+};
+
+int tegra_xusb_lane_parse_dt(struct tegra_xusb_lane *lane,
+ struct device_node *np);
+
+struct tegra_xusb_usb2_lane {
+ struct tegra_xusb_lane base;
+
+ u32 hs_curr_level_offset;
+};
+
+static inline struct tegra_xusb_usb2_lane *
+to_usb2_lane(struct tegra_xusb_lane *lane)
+{
+ return container_of(lane, struct tegra_xusb_usb2_lane, base);
+}
+
+struct tegra_xusb_ulpi_lane {
+ struct tegra_xusb_lane base;
+};
+
+static inline struct tegra_xusb_ulpi_lane *
+to_ulpi_lane(struct tegra_xusb_lane *lane)
+{
+ return container_of(lane, struct tegra_xusb_ulpi_lane, base);
+}
+
+struct tegra_xusb_hsic_lane {
+ struct tegra_xusb_lane base;
+
+ u32 strobe_trim;
+ u32 rx_strobe_trim;
+ u32 rx_data_trim;
+ u32 tx_rtune_n;
+ u32 tx_rtune_p;
+ u32 tx_rslew_n;
+ u32 tx_rslew_p;
+ bool auto_term;
+};
+
+static inline struct tegra_xusb_hsic_lane *
+to_hsic_lane(struct tegra_xusb_lane *lane)
+{
+ return container_of(lane, struct tegra_xusb_hsic_lane, base);
+}
+
+struct tegra_xusb_pcie_lane {
+ struct tegra_xusb_lane base;
+};
+
+static inline struct tegra_xusb_pcie_lane *
+to_pcie_lane(struct tegra_xusb_lane *lane)
+{
+ return container_of(lane, struct tegra_xusb_pcie_lane, base);
+}
+
+struct tegra_xusb_sata_lane {
+ struct tegra_xusb_lane base;
+};
+
+static inline struct tegra_xusb_sata_lane *
+to_sata_lane(struct tegra_xusb_lane *lane)
+{
+ return container_of(lane, struct tegra_xusb_sata_lane, base);
+}
+
+struct tegra_xusb_lane_ops {
+ struct tegra_xusb_lane *(*probe)(struct tegra_xusb_pad *pad,
+ struct device_node *np,
+ unsigned int index);
+ void (*remove)(struct tegra_xusb_lane *lane);
+};
+
+/*
+ * pads
+ */
+struct tegra_xusb_pad_soc;
+struct tegra_xusb_padctl;
+
+struct tegra_xusb_pad_ops {
+ struct tegra_xusb_pad *(*probe)(struct tegra_xusb_padctl *padctl,
+ const struct tegra_xusb_pad_soc *soc,
+ struct device_node *np);
+ void (*remove)(struct tegra_xusb_pad *pad);
+};
+
+struct tegra_xusb_pad_soc {
+ const char *name;
+
+ const struct tegra_xusb_lane_soc *lanes;
+ unsigned int num_lanes;
+
+ const struct tegra_xusb_pad_ops *ops;
+};
+
+struct tegra_xusb_pad {
+ const struct tegra_xusb_pad_soc *soc;
+ struct tegra_xusb_padctl *padctl;
+ struct phy_provider *provider;
+ struct phy **lanes;
+ struct device dev;
+
+ const struct tegra_xusb_lane_ops *ops;
+
+ struct list_head list;
+};
+
+static inline struct tegra_xusb_pad *to_tegra_xusb_pad(struct device *dev)
+{
+ return container_of(dev, struct tegra_xusb_pad, dev);
+}
+
+int tegra_xusb_pad_init(struct tegra_xusb_pad *pad,
+ struct tegra_xusb_padctl *padctl,
+ struct device_node *np);
+int tegra_xusb_pad_register(struct tegra_xusb_pad *pad,
+ const struct phy_ops *ops);
+void tegra_xusb_pad_unregister(struct tegra_xusb_pad *pad);
+
+struct tegra_xusb_usb2_pad {
+ struct tegra_xusb_pad base;
+
+ struct clk *clk;
+ unsigned int enable;
+ struct mutex lock;
+};
+
+static inline struct tegra_xusb_usb2_pad *
+to_usb2_pad(struct tegra_xusb_pad *pad)
+{
+ return container_of(pad, struct tegra_xusb_usb2_pad, base);
+}
+
+struct tegra_xusb_ulpi_pad {
+ struct tegra_xusb_pad base;
+};
+
+static inline struct tegra_xusb_ulpi_pad *
+to_ulpi_pad(struct tegra_xusb_pad *pad)
+{
+ return container_of(pad, struct tegra_xusb_ulpi_pad, base);
+}
+
+struct tegra_xusb_hsic_pad {
+ struct tegra_xusb_pad base;
+
+ struct regulator *supply;
+ struct clk *clk;
+};
+
+static inline struct tegra_xusb_hsic_pad *
+to_hsic_pad(struct tegra_xusb_pad *pad)
+{
+ return container_of(pad, struct tegra_xusb_hsic_pad, base);
+}
+
+struct tegra_xusb_pcie_pad {
+ struct tegra_xusb_pad base;
+
+ struct reset_control *rst;
+ struct clk *pll;
+
+ unsigned int enable;
+};
+
+static inline struct tegra_xusb_pcie_pad *
+to_pcie_pad(struct tegra_xusb_pad *pad)
+{
+ return container_of(pad, struct tegra_xusb_pcie_pad, base);
+}
+
+struct tegra_xusb_sata_pad {
+ struct tegra_xusb_pad base;
+
+ struct reset_control *rst;
+ struct clk *pll;
+
+ unsigned int enable;
+};
+
+static inline struct tegra_xusb_sata_pad *
+to_sata_pad(struct tegra_xusb_pad *pad)
+{
+ return container_of(pad, struct tegra_xusb_sata_pad, base);
+}
+
+/*
+ * ports
+ */
+struct tegra_xusb_port_ops;
+
+struct tegra_xusb_port {
+ struct tegra_xusb_padctl *padctl;
+ struct tegra_xusb_lane *lane;
+ unsigned int index;
+
+ struct list_head list;
+ struct device dev;
+
+ const struct tegra_xusb_port_ops *ops;
+};
+
+struct tegra_xusb_lane_map {
+ unsigned int port;
+ const char *type;
+ unsigned int index;
+ const char *func;
+};
+
+struct tegra_xusb_lane *
+tegra_xusb_port_find_lane(struct tegra_xusb_port *port,
+ const struct tegra_xusb_lane_map *map,
+ const char *function);
+
+struct tegra_xusb_port *
+tegra_xusb_find_port(struct tegra_xusb_padctl *padctl, const char *type,
+ unsigned int index);
+
+struct tegra_xusb_usb2_port {
+ struct tegra_xusb_port base;
+
+ struct regulator *supply;
+ bool internal;
+};
+
+static inline struct tegra_xusb_usb2_port *
+to_usb2_port(struct tegra_xusb_port *port)
+{
+ return container_of(port, struct tegra_xusb_usb2_port, base);
+}
+
+struct tegra_xusb_usb2_port *
+tegra_xusb_find_usb2_port(struct tegra_xusb_padctl *padctl,
+ unsigned int index);
+
+struct tegra_xusb_ulpi_port {
+ struct tegra_xusb_port base;
+
+ struct regulator *supply;
+ bool internal;
+};
+
+static inline struct tegra_xusb_ulpi_port *
+to_ulpi_port(struct tegra_xusb_port *port)
+{
+ return container_of(port, struct tegra_xusb_ulpi_port, base);
+}
+
+struct tegra_xusb_hsic_port {
+ struct tegra_xusb_port base;
+};
+
+static inline struct tegra_xusb_hsic_port *
+to_hsic_port(struct tegra_xusb_port *port)
+{
+ return container_of(port, struct tegra_xusb_hsic_port, base);
+}
+
+struct tegra_xusb_usb3_port {
+ struct tegra_xusb_port base;
+ struct regulator *supply;
+ bool context_saved;
+ unsigned int port;
+ bool internal;
+
+ u32 tap1;
+ u32 amp;
+ u32 ctle_z;
+ u32 ctle_g;
+};
+
+static inline struct tegra_xusb_usb3_port *
+to_usb3_port(struct tegra_xusb_port *port)
+{
+ return container_of(port, struct tegra_xusb_usb3_port, base);
+}
+
+struct tegra_xusb_usb3_port *
+tegra_xusb_find_usb3_port(struct tegra_xusb_padctl *padctl,
+ unsigned int index);
+
+struct tegra_xusb_port_ops {
+ int (*enable)(struct tegra_xusb_port *port);
+ void (*disable)(struct tegra_xusb_port *port);
+ struct tegra_xusb_lane *(*map)(struct tegra_xusb_port *port);
+};
+
+/*
+ * pad controller
+ */
+struct tegra_xusb_padctl_soc;
+
+struct tegra_xusb_padctl_ops {
+ struct tegra_xusb_padctl *
+ (*probe)(struct device *dev,
+ const struct tegra_xusb_padctl_soc *soc);
+ void (*remove)(struct tegra_xusb_padctl *padctl);
+
+ int (*usb3_save_context)(struct tegra_xusb_padctl *padctl,
+ unsigned int index);
+ int (*hsic_set_idle)(struct tegra_xusb_padctl *padctl,
+ unsigned int index, bool idle);
+ int (*usb3_set_lfps_detect)(struct tegra_xusb_padctl *padctl,
+ unsigned int index, bool enable);
+};
+
+struct tegra_xusb_padctl_soc {
+ const struct tegra_xusb_pad_soc * const *pads;
+ unsigned int num_pads;
+
+ struct {
+ struct {
+ const struct tegra_xusb_port_ops *ops;
+ unsigned int count;
+ } usb2, ulpi, hsic, usb3;
+ } ports;
+
+ const struct tegra_xusb_padctl_ops *ops;
+};
+
+struct tegra_xusb_padctl {
+ struct device *dev;
+ void __iomem *regs;
+ struct mutex lock;
+ struct reset_control *rst;
+
+ const struct tegra_xusb_padctl_soc *soc;
+
+ struct tegra_xusb_pad *pcie;
+ struct tegra_xusb_pad *sata;
+ struct tegra_xusb_pad *ulpi;
+ struct tegra_xusb_pad *usb2;
+ struct tegra_xusb_pad *hsic;
+
+ struct list_head ports;
+ struct list_head lanes;
+ struct list_head pads;
+
+ unsigned int enable;
+
+ struct clk *clk;
+};
+
+static inline void padctl_writel(struct tegra_xusb_padctl *padctl, u32 value,
+ unsigned long offset)
+{
+ dev_dbg(padctl->dev, "%08lx < %08x\n", offset, value);
+ writel(value, padctl->regs + offset);
+}
+
+static inline u32 padctl_readl(struct tegra_xusb_padctl *padctl,
+ unsigned long offset)
+{
+ u32 value = readl(padctl->regs + offset);
+ dev_dbg(padctl->dev, "%08lx > %08x\n", offset, value);
+ return value;
+}
+
+struct tegra_xusb_lane *tegra_xusb_find_lane(struct tegra_xusb_padctl *padctl,
+ const char *name,
+ unsigned int index);
+
+#if defined(CONFIG_ARCH_TEGRA_124_SOC) || defined(CONFIG_ARCH_TEGRA_132_SOC)
+extern const struct tegra_xusb_padctl_soc tegra124_xusb_padctl_soc;
+#endif
+#if defined(CONFIG_ARCH_TEGRA_210_SOC)
+extern const struct tegra_xusb_padctl_soc tegra210_xusb_padctl_soc;
+#endif
+
+#endif /* __PHY_TEGRA_XUSB_H */
diff --git a/drivers/phy/ti/Kconfig b/drivers/phy/ti/Kconfig
new file mode 100644
index 000000000..205035626
--- /dev/null
+++ b/drivers/phy/ti/Kconfig
@@ -0,0 +1,78 @@
+#
+# Phy drivers for TI platforms
+#
+config PHY_DA8XX_USB
+ tristate "TI DA8xx USB PHY Driver"
+ depends on ARCH_DAVINCI_DA8XX
+ select GENERIC_PHY
+ select MFD_SYSCON
+ help
+ Enable this to support the USB PHY on DA8xx SoCs.
+
+ This driver controls both the USB 1.1 PHY and the USB 2.0 PHY.
+
+config PHY_DM816X_USB
+ tristate "TI dm816x USB PHY driver"
+ depends on ARCH_OMAP2PLUS
+ depends on USB_SUPPORT
+ select GENERIC_PHY
+ select USB_PHY
+ help
+ Enable this for dm816x USB to work.
+
+config OMAP_CONTROL_PHY
+ tristate "OMAP CONTROL PHY Driver"
+ depends on ARCH_OMAP2PLUS || COMPILE_TEST
+ help
+ Enable this to add support for the PHY part present in the control
+ module. This driver has API to power on the USB2 PHY and to write to
+ the mailbox. The mailbox is present only in omap4 and the register to
+ power on the USB2 PHY is present in OMAP4 and OMAP5. OMAP5 has an
+ additional register to power on USB3 PHY/SATA PHY/PCIE PHY
+ (PIPE3 PHY).
+
+config OMAP_USB2
+ tristate "OMAP USB2 PHY Driver"
+ depends on ARCH_OMAP2PLUS
+ depends on USB_SUPPORT
+ select GENERIC_PHY
+ select USB_PHY
+ select OMAP_CONTROL_PHY
+ depends on OMAP_OCP2SCP
+ help
+ Enable this to support the transceiver that is part of SOC. This
+ driver takes care of all the PHY functionality apart from comparator.
+ The USB OTG controller communicates with the comparator using this
+ driver.
+
+config TI_PIPE3
+ tristate "TI PIPE3 PHY Driver"
+ depends on ARCH_OMAP2PLUS || COMPILE_TEST
+ select GENERIC_PHY
+ select OMAP_CONTROL_PHY
+ depends on OMAP_OCP2SCP
+ help
+ Enable this to support the PIPE3 PHY that is part of TI SOCs. This
+ driver takes care of all the PHY functionality apart from comparator.
+ This driver interacts with the "OMAP Control PHY Driver" to power
+ on/off the PHY.
+
+config PHY_TUSB1210
+ tristate "TI TUSB1210 ULPI PHY module"
+ depends on USB_ULPI_BUS
+ select GENERIC_PHY
+ help
+ Support for TI TUSB1210 USB ULPI PHY.
+
+config TWL4030_USB
+ tristate "TWL4030 USB Transceiver Driver"
+ depends on TWL4030_CORE && REGULATOR_TWL4030 && USB_MUSB_OMAP2PLUS
+ depends on USB_SUPPORT
+ depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't 'y'
+ select GENERIC_PHY
+ select USB_PHY
+ help
+ Enable this to support the USB OTG transceiver on TWL4030
+ family chips (including the TWL5030 and TPS659x0 devices).
+ This transceiver supports high and full speed devices plus,
+ in host mode, low speed.
diff --git a/drivers/phy/ti/Makefile b/drivers/phy/ti/Makefile
new file mode 100644
index 000000000..9f361756e
--- /dev/null
+++ b/drivers/phy/ti/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_PHY_DA8XX_USB) += phy-da8xx-usb.o
+obj-$(CONFIG_PHY_DM816X_USB) += phy-dm816x-usb.o
+obj-$(CONFIG_OMAP_CONTROL_PHY) += phy-omap-control.o
+obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o
+obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o
+obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o
+obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o
diff --git a/drivers/phy/ti/phy-da8xx-usb.c b/drivers/phy/ti/phy-da8xx-usb.c
new file mode 100644
index 000000000..befb886ff
--- /dev/null
+++ b/drivers/phy/ti/phy-da8xx-usb.c
@@ -0,0 +1,253 @@
+/*
+ * phy-da8xx-usb - TI DaVinci DA8xx USB PHY driver
+ *
+ * Copyright (C) 2016 David Lechner <david@lechnology.com>
+ *
+ * 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; 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/io.h>
+#include <linux/of.h>
+#include <linux/mfd/da8xx-cfgchip.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_data/phy-da8xx-usb.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define PHY_INIT_BITS (CFGCHIP2_SESENDEN | CFGCHIP2_VBDTCTEN)
+
+struct da8xx_usb_phy {
+ struct phy_provider *phy_provider;
+ struct phy *usb11_phy;
+ struct phy *usb20_phy;
+ struct clk *usb11_clk;
+ struct clk *usb20_clk;
+ struct regmap *regmap;
+};
+
+static int da8xx_usb11_phy_power_on(struct phy *phy)
+{
+ struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy);
+ int ret;
+
+ ret = clk_prepare_enable(d_phy->usb11_clk);
+ if (ret)
+ return ret;
+
+ regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_USB1SUSPENDM,
+ CFGCHIP2_USB1SUSPENDM);
+
+ return 0;
+}
+
+static int da8xx_usb11_phy_power_off(struct phy *phy)
+{
+ struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy);
+
+ regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_USB1SUSPENDM, 0);
+
+ clk_disable_unprepare(d_phy->usb11_clk);
+
+ return 0;
+}
+
+static const struct phy_ops da8xx_usb11_phy_ops = {
+ .power_on = da8xx_usb11_phy_power_on,
+ .power_off = da8xx_usb11_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int da8xx_usb20_phy_power_on(struct phy *phy)
+{
+ struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy);
+ int ret;
+
+ ret = clk_prepare_enable(d_phy->usb20_clk);
+ if (ret)
+ return ret;
+
+ regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_OTGPWRDN, 0);
+
+ return 0;
+}
+
+static int da8xx_usb20_phy_power_off(struct phy *phy)
+{
+ struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy);
+
+ regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_OTGPWRDN,
+ CFGCHIP2_OTGPWRDN);
+
+ clk_disable_unprepare(d_phy->usb20_clk);
+
+ return 0;
+}
+
+static int da8xx_usb20_phy_set_mode(struct phy *phy, enum phy_mode mode)
+{
+ struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy);
+ u32 val;
+
+ switch (mode) {
+ case PHY_MODE_USB_HOST: /* Force VBUS valid, ID = 0 */
+ val = CFGCHIP2_OTGMODE_FORCE_HOST;
+ break;
+ case PHY_MODE_USB_DEVICE: /* Force VBUS valid, ID = 1 */
+ val = CFGCHIP2_OTGMODE_FORCE_DEVICE;
+ break;
+ case PHY_MODE_USB_OTG: /* Don't override the VBUS/ID comparators */
+ val = CFGCHIP2_OTGMODE_NO_OVERRIDE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_OTGMODE_MASK,
+ val);
+
+ return 0;
+}
+
+static const struct phy_ops da8xx_usb20_phy_ops = {
+ .power_on = da8xx_usb20_phy_power_on,
+ .power_off = da8xx_usb20_phy_power_off,
+ .set_mode = da8xx_usb20_phy_set_mode,
+ .owner = THIS_MODULE,
+};
+
+static struct phy *da8xx_usb_phy_of_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct da8xx_usb_phy *d_phy = dev_get_drvdata(dev);
+
+ if (!d_phy)
+ return ERR_PTR(-ENODEV);
+
+ switch (args->args[0]) {
+ case 0:
+ return d_phy->usb20_phy;
+ case 1:
+ return d_phy->usb11_phy;
+ default:
+ return ERR_PTR(-EINVAL);
+ }
+}
+
+static int da8xx_usb_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct da8xx_usb_phy_platform_data *pdata = dev->platform_data;
+ struct device_node *node = dev->of_node;
+ struct da8xx_usb_phy *d_phy;
+
+ d_phy = devm_kzalloc(dev, sizeof(*d_phy), GFP_KERNEL);
+ if (!d_phy)
+ return -ENOMEM;
+
+ if (pdata)
+ d_phy->regmap = pdata->cfgchip;
+ else
+ d_phy->regmap = syscon_regmap_lookup_by_compatible(
+ "ti,da830-cfgchip");
+ if (IS_ERR(d_phy->regmap)) {
+ dev_err(dev, "Failed to get syscon\n");
+ return PTR_ERR(d_phy->regmap);
+ }
+
+ d_phy->usb11_clk = devm_clk_get(dev, "usb1_clk48");
+ if (IS_ERR(d_phy->usb11_clk)) {
+ dev_err(dev, "Failed to get usb1_clk48\n");
+ return PTR_ERR(d_phy->usb11_clk);
+ }
+
+ d_phy->usb20_clk = devm_clk_get(dev, "usb0_clk48");
+ if (IS_ERR(d_phy->usb20_clk)) {
+ dev_err(dev, "Failed to get usb0_clk48\n");
+ return PTR_ERR(d_phy->usb20_clk);
+ }
+
+ d_phy->usb11_phy = devm_phy_create(dev, node, &da8xx_usb11_phy_ops);
+ if (IS_ERR(d_phy->usb11_phy)) {
+ dev_err(dev, "Failed to create usb11 phy\n");
+ return PTR_ERR(d_phy->usb11_phy);
+ }
+
+ d_phy->usb20_phy = devm_phy_create(dev, node, &da8xx_usb20_phy_ops);
+ if (IS_ERR(d_phy->usb20_phy)) {
+ dev_err(dev, "Failed to create usb20 phy\n");
+ return PTR_ERR(d_phy->usb20_phy);
+ }
+
+ platform_set_drvdata(pdev, d_phy);
+ phy_set_drvdata(d_phy->usb11_phy, d_phy);
+ phy_set_drvdata(d_phy->usb20_phy, d_phy);
+
+ if (node) {
+ d_phy->phy_provider = devm_of_phy_provider_register(dev,
+ da8xx_usb_phy_of_xlate);
+ if (IS_ERR(d_phy->phy_provider)) {
+ dev_err(dev, "Failed to create phy provider\n");
+ return PTR_ERR(d_phy->phy_provider);
+ }
+ } else {
+ int ret;
+
+ ret = phy_create_lookup(d_phy->usb11_phy, "usb-phy",
+ "ohci-da8xx");
+ if (ret)
+ dev_warn(dev, "Failed to create usb11 phy lookup\n");
+ ret = phy_create_lookup(d_phy->usb20_phy, "usb-phy",
+ "musb-da8xx");
+ if (ret)
+ dev_warn(dev, "Failed to create usb20 phy lookup\n");
+ }
+
+ regmap_write_bits(d_phy->regmap, CFGCHIP(2),
+ PHY_INIT_BITS, PHY_INIT_BITS);
+
+ return 0;
+}
+
+static int da8xx_usb_phy_remove(struct platform_device *pdev)
+{
+ struct da8xx_usb_phy *d_phy = platform_get_drvdata(pdev);
+
+ if (!pdev->dev.of_node) {
+ phy_remove_lookup(d_phy->usb20_phy, "usb-phy", "musb-da8xx");
+ phy_remove_lookup(d_phy->usb11_phy, "usb-phy", "ohci-da8xx");
+ }
+
+ return 0;
+}
+
+static const struct of_device_id da8xx_usb_phy_ids[] = {
+ { .compatible = "ti,da830-usb-phy" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, da8xx_usb_phy_ids);
+
+static struct platform_driver da8xx_usb_phy_driver = {
+ .probe = da8xx_usb_phy_probe,
+ .remove = da8xx_usb_phy_remove,
+ .driver = {
+ .name = "da8xx-usb-phy",
+ .of_match_table = da8xx_usb_phy_ids,
+ },
+};
+
+module_platform_driver(da8xx_usb_phy_driver);
+
+MODULE_ALIAS("platform:da8xx-usb-phy");
+MODULE_AUTHOR("David Lechner <david@lechnology.com>");
+MODULE_DESCRIPTION("TI DA8xx USB PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/ti/phy-dm816x-usb.c b/drivers/phy/ti/phy-dm816x-usb.c
new file mode 100644
index 000000000..2ed5fe20d
--- /dev/null
+++ b/drivers/phy/ti/phy-dm816x-usb.c
@@ -0,0 +1,299 @@
+/*
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/io.h>
+#include <linux/usb/phy_companion.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/pm_runtime.h>
+#include <linux/delay.h>
+#include <linux/phy/phy.h>
+#include <linux/of_platform.h>
+
+#include <linux/mfd/syscon.h>
+
+/*
+ * TRM has two sets of USB_CTRL registers.. The correct register bits
+ * are in TRM section 24.9.8.2 USB_CTRL Register. The TRM documents the
+ * phy as being SR70LX Synopsys USB 2.0 OTG nanoPHY. It also seems at
+ * least dm816x rev c ignores writes to USB_CTRL register, but the TI
+ * kernel is writing to those so it's possible that later revisions
+ * have worknig USB_CTRL register.
+ *
+ * Also note that At least USB_CTRL register seems to be dm816x specific
+ * according to the TRM. It's possible that USBPHY_CTRL is more generic,
+ * but that would have to be checked against the SR70LX documentation
+ * which does not seem to be publicly available.
+ *
+ * Finally, the phy on dm814x and am335x is different from dm816x.
+ */
+#define DM816X_USB_CTRL_PHYCLKSRC BIT(8) /* 1 = PLL ref clock */
+#define DM816X_USB_CTRL_PHYSLEEP1 BIT(1) /* Enable the first phy */
+#define DM816X_USB_CTRL_PHYSLEEP0 BIT(0) /* Enable the second phy */
+
+#define DM816X_USBPHY_CTRL_TXRISETUNE 1
+#define DM816X_USBPHY_CTRL_TXVREFTUNE 0xc
+#define DM816X_USBPHY_CTRL_TXPREEMTUNE 0x2
+
+struct dm816x_usb_phy {
+ struct regmap *syscon;
+ struct device *dev;
+ unsigned int instance;
+ struct clk *refclk;
+ struct usb_phy phy;
+ unsigned int usb_ctrl; /* Shared between phy0 and phy1 */
+ unsigned int usbphy_ctrl;
+};
+
+static int dm816x_usb_phy_set_host(struct usb_otg *otg, struct usb_bus *host)
+{
+ otg->host = host;
+ if (!host)
+ otg->state = OTG_STATE_UNDEFINED;
+
+ return 0;
+}
+
+static int dm816x_usb_phy_set_peripheral(struct usb_otg *otg,
+ struct usb_gadget *gadget)
+{
+ otg->gadget = gadget;
+ if (!gadget)
+ otg->state = OTG_STATE_UNDEFINED;
+
+ return 0;
+}
+
+static int dm816x_usb_phy_init(struct phy *x)
+{
+ struct dm816x_usb_phy *phy = phy_get_drvdata(x);
+ unsigned int val;
+ int error;
+
+ if (clk_get_rate(phy->refclk) != 24000000)
+ dev_warn(phy->dev, "nonstandard phy refclk\n");
+
+ /* Set PLL ref clock and put phys to sleep */
+ error = regmap_update_bits(phy->syscon, phy->usb_ctrl,
+ DM816X_USB_CTRL_PHYCLKSRC |
+ DM816X_USB_CTRL_PHYSLEEP1 |
+ DM816X_USB_CTRL_PHYSLEEP0,
+ 0);
+ regmap_read(phy->syscon, phy->usb_ctrl, &val);
+ if ((val & 3) != 0)
+ dev_info(phy->dev,
+ "Working dm816x USB_CTRL! (0x%08x)\n",
+ val);
+
+ /*
+ * TI kernel sets these values for "symmetrical eye diagram and
+ * better signal quality" so let's assume somebody checked the
+ * values with a scope and set them here too.
+ */
+ regmap_read(phy->syscon, phy->usbphy_ctrl, &val);
+ val |= DM816X_USBPHY_CTRL_TXRISETUNE |
+ DM816X_USBPHY_CTRL_TXVREFTUNE |
+ DM816X_USBPHY_CTRL_TXPREEMTUNE;
+ regmap_write(phy->syscon, phy->usbphy_ctrl, val);
+
+ return 0;
+}
+
+static const struct phy_ops ops = {
+ .init = dm816x_usb_phy_init,
+ .owner = THIS_MODULE,
+};
+
+static int __maybe_unused dm816x_usb_phy_runtime_suspend(struct device *dev)
+{
+ struct dm816x_usb_phy *phy = dev_get_drvdata(dev);
+ unsigned int mask, val;
+ int error = 0;
+
+ mask = BIT(phy->instance);
+ val = ~BIT(phy->instance);
+ error = regmap_update_bits(phy->syscon, phy->usb_ctrl,
+ mask, val);
+ if (error)
+ dev_err(phy->dev, "phy%i failed to power off\n",
+ phy->instance);
+ clk_disable(phy->refclk);
+
+ return 0;
+}
+
+static int __maybe_unused dm816x_usb_phy_runtime_resume(struct device *dev)
+{
+ struct dm816x_usb_phy *phy = dev_get_drvdata(dev);
+ unsigned int mask, val;
+ int error;
+
+ error = clk_enable(phy->refclk);
+ if (error)
+ return error;
+
+ /*
+ * Note that at least dm816x rev c does not seem to do
+ * anything with the USB_CTRL register. But let's follow
+ * what the TI tree is doing in case later revisions use
+ * USB_CTRL.
+ */
+ mask = BIT(phy->instance);
+ val = BIT(phy->instance);
+ error = regmap_update_bits(phy->syscon, phy->usb_ctrl,
+ mask, val);
+ if (error) {
+ dev_err(phy->dev, "phy%i failed to power on\n",
+ phy->instance);
+ clk_disable(phy->refclk);
+ return error;
+ }
+
+ return 0;
+}
+
+static UNIVERSAL_DEV_PM_OPS(dm816x_usb_phy_pm_ops,
+ dm816x_usb_phy_runtime_suspend,
+ dm816x_usb_phy_runtime_resume,
+ NULL);
+
+#ifdef CONFIG_OF
+static const struct of_device_id dm816x_usb_phy_id_table[] = {
+ {
+ .compatible = "ti,dm8168-usb-phy",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, dm816x_usb_phy_id_table);
+#endif
+
+static int dm816x_usb_phy_probe(struct platform_device *pdev)
+{
+ struct dm816x_usb_phy *phy;
+ struct resource *res;
+ struct phy *generic_phy;
+ struct phy_provider *phy_provider;
+ struct usb_otg *otg;
+ const struct of_device_id *of_id;
+ const struct usb_phy_data *phy_data;
+ int error;
+
+ of_id = of_match_device(of_match_ptr(dm816x_usb_phy_id_table),
+ &pdev->dev);
+ if (!of_id)
+ return -EINVAL;
+
+ phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENOENT;
+
+ phy->syscon = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+ "syscon");
+ if (IS_ERR(phy->syscon))
+ return PTR_ERR(phy->syscon);
+
+ /*
+ * According to sprs614e.pdf, the first usb_ctrl is shared and
+ * the second instance for usb_ctrl is reserved.. Also the
+ * register bits are different from earlier TRMs.
+ */
+ phy->usb_ctrl = 0x20;
+ phy->usbphy_ctrl = (res->start & 0xff) + 4;
+ if (phy->usbphy_ctrl == 0x2c)
+ phy->instance = 1;
+
+ phy_data = of_id->data;
+
+ otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL);
+ if (!otg)
+ return -ENOMEM;
+
+ phy->dev = &pdev->dev;
+ phy->phy.dev = phy->dev;
+ phy->phy.label = "dm8168_usb_phy";
+ phy->phy.otg = otg;
+ phy->phy.type = USB_PHY_TYPE_USB2;
+ otg->set_host = dm816x_usb_phy_set_host;
+ otg->set_peripheral = dm816x_usb_phy_set_peripheral;
+ otg->usb_phy = &phy->phy;
+
+ platform_set_drvdata(pdev, phy);
+
+ phy->refclk = devm_clk_get(phy->dev, "refclk");
+ if (IS_ERR(phy->refclk))
+ return PTR_ERR(phy->refclk);
+ error = clk_prepare(phy->refclk);
+ if (error)
+ return error;
+
+ pm_runtime_enable(phy->dev);
+ generic_phy = devm_phy_create(phy->dev, NULL, &ops);
+ if (IS_ERR(generic_phy)) {
+ error = PTR_ERR(generic_phy);
+ goto clk_unprepare;
+ }
+
+ phy_set_drvdata(generic_phy, phy);
+
+ phy_provider = devm_of_phy_provider_register(phy->dev,
+ of_phy_simple_xlate);
+ if (IS_ERR(phy_provider)) {
+ error = PTR_ERR(phy_provider);
+ goto clk_unprepare;
+ }
+
+ usb_add_phy_dev(&phy->phy);
+
+ return 0;
+
+clk_unprepare:
+ pm_runtime_disable(phy->dev);
+ clk_unprepare(phy->refclk);
+ return error;
+}
+
+static int dm816x_usb_phy_remove(struct platform_device *pdev)
+{
+ struct dm816x_usb_phy *phy = platform_get_drvdata(pdev);
+
+ usb_remove_phy(&phy->phy);
+ pm_runtime_disable(phy->dev);
+ clk_unprepare(phy->refclk);
+
+ return 0;
+}
+
+static struct platform_driver dm816x_usb_phy_driver = {
+ .probe = dm816x_usb_phy_probe,
+ .remove = dm816x_usb_phy_remove,
+ .driver = {
+ .name = "dm816x-usb-phy",
+ .pm = &dm816x_usb_phy_pm_ops,
+ .of_match_table = of_match_ptr(dm816x_usb_phy_id_table),
+ },
+};
+
+module_platform_driver(dm816x_usb_phy_driver);
+
+MODULE_ALIAS("platform:dm816x_usb");
+MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
+MODULE_DESCRIPTION("dm816x usb phy driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/ti/phy-omap-control.c b/drivers/phy/ti/phy-omap-control.c
new file mode 100644
index 000000000..e9c41b3fa
--- /dev/null
+++ b/drivers/phy/ti/phy-omap-control.c
@@ -0,0 +1,360 @@
+/*
+ * omap-control-phy.c - The PHY part of control module.
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
+ * 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.
+ *
+ * Author: Kishon Vijay Abraham I <kishon@ti.com>
+ *
+ * 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/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/phy/omap_control_phy.h>
+
+/**
+ * omap_control_pcie_pcs - set the PCS delay count
+ * @dev: the control module device
+ * @delay: 8 bit delay value
+ */
+void omap_control_pcie_pcs(struct device *dev, u8 delay)
+{
+ u32 val;
+ struct omap_control_phy *control_phy;
+
+ if (IS_ERR(dev) || !dev) {
+ pr_err("%s: invalid device\n", __func__);
+ return;
+ }
+
+ control_phy = dev_get_drvdata(dev);
+ if (!control_phy) {
+ dev_err(dev, "%s: invalid control phy device\n", __func__);
+ return;
+ }
+
+ if (control_phy->type != OMAP_CTRL_TYPE_PCIE) {
+ dev_err(dev, "%s: unsupported operation\n", __func__);
+ return;
+ }
+
+ val = readl(control_phy->pcie_pcs);
+ val &= ~(OMAP_CTRL_PCIE_PCS_MASK <<
+ OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT);
+ val |= (delay << OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT);
+ writel(val, control_phy->pcie_pcs);
+}
+EXPORT_SYMBOL_GPL(omap_control_pcie_pcs);
+
+/**
+ * omap_control_phy_power - power on/off the phy using control module reg
+ * @dev: the control module device
+ * @on: 0 or 1, based on powering on or off the PHY
+ */
+void omap_control_phy_power(struct device *dev, int on)
+{
+ u32 val;
+ unsigned long rate;
+ struct omap_control_phy *control_phy;
+
+ if (IS_ERR(dev) || !dev) {
+ pr_err("%s: invalid device\n", __func__);
+ return;
+ }
+
+ control_phy = dev_get_drvdata(dev);
+ if (!control_phy) {
+ dev_err(dev, "%s: invalid control phy device\n", __func__);
+ return;
+ }
+
+ if (control_phy->type == OMAP_CTRL_TYPE_OTGHS)
+ return;
+
+ val = readl(control_phy->power);
+
+ switch (control_phy->type) {
+ case OMAP_CTRL_TYPE_USB2:
+ if (on)
+ val &= ~OMAP_CTRL_DEV_PHY_PD;
+ else
+ val |= OMAP_CTRL_DEV_PHY_PD;
+ break;
+
+ case OMAP_CTRL_TYPE_PCIE:
+ case OMAP_CTRL_TYPE_PIPE3:
+ rate = clk_get_rate(control_phy->sys_clk);
+ rate = rate/1000000;
+
+ if (on) {
+ val &= ~(OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK |
+ OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK);
+ val |= OMAP_CTRL_PIPE3_PHY_TX_RX_POWERON <<
+ OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT;
+ val |= rate <<
+ OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT;
+ } else {
+ val &= ~OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK;
+ val |= OMAP_CTRL_PIPE3_PHY_TX_RX_POWEROFF <<
+ OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT;
+ }
+ break;
+
+ case OMAP_CTRL_TYPE_DRA7USB2:
+ if (on)
+ val &= ~OMAP_CTRL_USB2_PHY_PD;
+ else
+ val |= OMAP_CTRL_USB2_PHY_PD;
+ break;
+
+ case OMAP_CTRL_TYPE_AM437USB2:
+ if (on) {
+ val &= ~(AM437X_CTRL_USB2_PHY_PD |
+ AM437X_CTRL_USB2_OTG_PD);
+ val |= (AM437X_CTRL_USB2_OTGVDET_EN |
+ AM437X_CTRL_USB2_OTGSESSEND_EN);
+ } else {
+ val &= ~(AM437X_CTRL_USB2_OTGVDET_EN |
+ AM437X_CTRL_USB2_OTGSESSEND_EN);
+ val |= (AM437X_CTRL_USB2_PHY_PD |
+ AM437X_CTRL_USB2_OTG_PD);
+ }
+ break;
+ default:
+ dev_err(dev, "%s: type %d not recognized\n",
+ __func__, control_phy->type);
+ break;
+ }
+
+ writel(val, control_phy->power);
+}
+EXPORT_SYMBOL_GPL(omap_control_phy_power);
+
+/**
+ * omap_control_usb_host_mode - set AVALID, VBUSVALID and ID pin in grounded
+ * @ctrl_phy: struct omap_control_phy *
+ *
+ * Writes to the mailbox register to notify the usb core that a usb
+ * device has been connected.
+ */
+static void omap_control_usb_host_mode(struct omap_control_phy *ctrl_phy)
+{
+ u32 val;
+
+ val = readl(ctrl_phy->otghs_control);
+ val &= ~(OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_SESSEND);
+ val |= OMAP_CTRL_DEV_AVALID | OMAP_CTRL_DEV_VBUSVALID;
+ writel(val, ctrl_phy->otghs_control);
+}
+
+/**
+ * omap_control_usb_device_mode - set AVALID, VBUSVALID and ID pin in high
+ * impedance
+ * @ctrl_phy: struct omap_control_phy *
+ *
+ * Writes to the mailbox register to notify the usb core that it has been
+ * connected to a usb host.
+ */
+static void omap_control_usb_device_mode(struct omap_control_phy *ctrl_phy)
+{
+ u32 val;
+
+ val = readl(ctrl_phy->otghs_control);
+ val &= ~OMAP_CTRL_DEV_SESSEND;
+ val |= OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_AVALID |
+ OMAP_CTRL_DEV_VBUSVALID;
+ writel(val, ctrl_phy->otghs_control);
+}
+
+/**
+ * omap_control_usb_set_sessionend - Enable SESSIONEND and IDIG to high
+ * impedance
+ * @ctrl_phy: struct omap_control_phy *
+ *
+ * Writes to the mailbox register to notify the usb core it's now in
+ * disconnected state.
+ */
+static void omap_control_usb_set_sessionend(struct omap_control_phy *ctrl_phy)
+{
+ u32 val;
+
+ val = readl(ctrl_phy->otghs_control);
+ val &= ~(OMAP_CTRL_DEV_AVALID | OMAP_CTRL_DEV_VBUSVALID);
+ val |= OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_SESSEND;
+ writel(val, ctrl_phy->otghs_control);
+}
+
+/**
+ * omap_control_usb_set_mode - Calls to functions to set USB in one of host mode
+ * or device mode or to denote disconnected state
+ * @dev: the control module device
+ * @mode: The mode to which usb should be configured
+ *
+ * This is an API to write to the mailbox register to notify the usb core that
+ * a usb device has been connected.
+ */
+void omap_control_usb_set_mode(struct device *dev,
+ enum omap_control_usb_mode mode)
+{
+ struct omap_control_phy *ctrl_phy;
+
+ if (IS_ERR(dev) || !dev)
+ return;
+
+ ctrl_phy = dev_get_drvdata(dev);
+ if (!ctrl_phy) {
+ dev_err(dev, "Invalid control phy device\n");
+ return;
+ }
+
+ if (ctrl_phy->type != OMAP_CTRL_TYPE_OTGHS)
+ return;
+
+ switch (mode) {
+ case USB_MODE_HOST:
+ omap_control_usb_host_mode(ctrl_phy);
+ break;
+ case USB_MODE_DEVICE:
+ omap_control_usb_device_mode(ctrl_phy);
+ break;
+ case USB_MODE_DISCONNECT:
+ omap_control_usb_set_sessionend(ctrl_phy);
+ break;
+ default:
+ dev_vdbg(dev, "invalid omap control usb mode\n");
+ }
+}
+EXPORT_SYMBOL_GPL(omap_control_usb_set_mode);
+
+static const enum omap_control_phy_type otghs_data = OMAP_CTRL_TYPE_OTGHS;
+static const enum omap_control_phy_type usb2_data = OMAP_CTRL_TYPE_USB2;
+static const enum omap_control_phy_type pipe3_data = OMAP_CTRL_TYPE_PIPE3;
+static const enum omap_control_phy_type pcie_data = OMAP_CTRL_TYPE_PCIE;
+static const enum omap_control_phy_type dra7usb2_data = OMAP_CTRL_TYPE_DRA7USB2;
+static const enum omap_control_phy_type am437usb2_data = OMAP_CTRL_TYPE_AM437USB2;
+
+static const struct of_device_id omap_control_phy_id_table[] = {
+ {
+ .compatible = "ti,control-phy-otghs",
+ .data = &otghs_data,
+ },
+ {
+ .compatible = "ti,control-phy-usb2",
+ .data = &usb2_data,
+ },
+ {
+ .compatible = "ti,control-phy-pipe3",
+ .data = &pipe3_data,
+ },
+ {
+ .compatible = "ti,control-phy-pcie",
+ .data = &pcie_data,
+ },
+ {
+ .compatible = "ti,control-phy-usb2-dra7",
+ .data = &dra7usb2_data,
+ },
+ {
+ .compatible = "ti,control-phy-usb2-am437",
+ .data = &am437usb2_data,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, omap_control_phy_id_table);
+
+static int omap_control_phy_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ const struct of_device_id *of_id;
+ struct omap_control_phy *control_phy;
+
+ of_id = of_match_device(omap_control_phy_id_table, &pdev->dev);
+ if (!of_id)
+ return -EINVAL;
+
+ control_phy = devm_kzalloc(&pdev->dev, sizeof(*control_phy),
+ GFP_KERNEL);
+ if (!control_phy)
+ return -ENOMEM;
+
+ control_phy->dev = &pdev->dev;
+ control_phy->type = *(enum omap_control_phy_type *)of_id->data;
+
+ if (control_phy->type == OMAP_CTRL_TYPE_OTGHS) {
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "otghs_control");
+ control_phy->otghs_control = devm_ioremap_resource(
+ &pdev->dev, res);
+ if (IS_ERR(control_phy->otghs_control))
+ return PTR_ERR(control_phy->otghs_control);
+ } else {
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "power");
+ control_phy->power = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(control_phy->power)) {
+ dev_err(&pdev->dev, "Couldn't get power register\n");
+ return PTR_ERR(control_phy->power);
+ }
+ }
+
+ if (control_phy->type == OMAP_CTRL_TYPE_PIPE3 ||
+ control_phy->type == OMAP_CTRL_TYPE_PCIE) {
+ control_phy->sys_clk = devm_clk_get(control_phy->dev,
+ "sys_clkin");
+ if (IS_ERR(control_phy->sys_clk)) {
+ pr_err("%s: unable to get sys_clkin\n", __func__);
+ return -EINVAL;
+ }
+ }
+
+ if (control_phy->type == OMAP_CTRL_TYPE_PCIE) {
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "pcie_pcs");
+ control_phy->pcie_pcs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(control_phy->pcie_pcs))
+ return PTR_ERR(control_phy->pcie_pcs);
+ }
+
+ dev_set_drvdata(control_phy->dev, control_phy);
+
+ return 0;
+}
+
+static struct platform_driver omap_control_phy_driver = {
+ .probe = omap_control_phy_probe,
+ .driver = {
+ .name = "omap-control-phy",
+ .of_match_table = omap_control_phy_id_table,
+ },
+};
+
+static int __init omap_control_phy_init(void)
+{
+ return platform_driver_register(&omap_control_phy_driver);
+}
+subsys_initcall(omap_control_phy_init);
+
+static void __exit omap_control_phy_exit(void)
+{
+ platform_driver_unregister(&omap_control_phy_driver);
+}
+module_exit(omap_control_phy_exit);
+
+MODULE_ALIAS("platform:omap_control_phy");
+MODULE_AUTHOR("Texas Instruments Inc.");
+MODULE_DESCRIPTION("OMAP Control Module PHY Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/ti/phy-omap-usb2.c b/drivers/phy/ti/phy-omap-usb2.c
new file mode 100644
index 000000000..fe909fd81
--- /dev/null
+++ b/drivers/phy/ti/phy-omap-usb2.c
@@ -0,0 +1,439 @@
+/*
+ * omap-usb2.c - USB PHY, talking to musb controller in OMAP.
+ *
+ * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com
+ * 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.
+ *
+ * Author: Kishon Vijay Abraham I <kishon@ti.com>
+ *
+ * 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/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/io.h>
+#include <linux/phy/omap_usb.h>
+#include <linux/usb/phy_companion.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/pm_runtime.h>
+#include <linux/delay.h>
+#include <linux/phy/omap_control_phy.h>
+#include <linux/phy/phy.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/of_platform.h>
+
+#define USB2PHY_DISCON_BYP_LATCH (1 << 31)
+#define USB2PHY_ANA_CONFIG1 0x4c
+
+/**
+ * omap_usb2_set_comparator - links the comparator present in the sytem with
+ * this phy
+ * @comparator - the companion phy(comparator) for this phy
+ *
+ * The phy companion driver should call this API passing the phy_companion
+ * filled with set_vbus and start_srp to be used by usb phy.
+ *
+ * For use by phy companion driver
+ */
+int omap_usb2_set_comparator(struct phy_companion *comparator)
+{
+ struct omap_usb *phy;
+ struct usb_phy *x = usb_get_phy(USB_PHY_TYPE_USB2);
+
+ if (IS_ERR(x))
+ return -ENODEV;
+
+ phy = phy_to_omapusb(x);
+ phy->comparator = comparator;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(omap_usb2_set_comparator);
+
+static int omap_usb_set_vbus(struct usb_otg *otg, bool enabled)
+{
+ struct omap_usb *phy = phy_to_omapusb(otg->usb_phy);
+
+ if (!phy->comparator)
+ return -ENODEV;
+
+ return phy->comparator->set_vbus(phy->comparator, enabled);
+}
+
+static int omap_usb_start_srp(struct usb_otg *otg)
+{
+ struct omap_usb *phy = phy_to_omapusb(otg->usb_phy);
+
+ if (!phy->comparator)
+ return -ENODEV;
+
+ return phy->comparator->start_srp(phy->comparator);
+}
+
+static int omap_usb_set_host(struct usb_otg *otg, struct usb_bus *host)
+{
+ otg->host = host;
+ if (!host)
+ otg->state = OTG_STATE_UNDEFINED;
+
+ return 0;
+}
+
+static int omap_usb_set_peripheral(struct usb_otg *otg,
+ struct usb_gadget *gadget)
+{
+ otg->gadget = gadget;
+ if (!gadget)
+ otg->state = OTG_STATE_UNDEFINED;
+
+ return 0;
+}
+
+static int omap_usb_phy_power(struct omap_usb *phy, int on)
+{
+ u32 val;
+ int ret;
+
+ if (!phy->syscon_phy_power) {
+ omap_control_phy_power(phy->control_dev, on);
+ return 0;
+ }
+
+ if (on)
+ val = phy->power_on;
+ else
+ val = phy->power_off;
+
+ ret = regmap_update_bits(phy->syscon_phy_power, phy->power_reg,
+ phy->mask, val);
+ return ret;
+}
+
+static int omap_usb_power_off(struct phy *x)
+{
+ struct omap_usb *phy = phy_get_drvdata(x);
+
+ return omap_usb_phy_power(phy, false);
+}
+
+static int omap_usb_power_on(struct phy *x)
+{
+ struct omap_usb *phy = phy_get_drvdata(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) {
+ /*
+ *
+ * Reduce the sensitivity of internal PHY by enabling the
+ * DISCON_BYP_LATCH of the USB2PHY_ANA_CONFIG1 register. This
+ * resolves issues with certain devices which can otherwise
+ * be prone to false disconnects.
+ *
+ */
+ val = omap_usb_readl(phy->phy_base, USB2PHY_ANA_CONFIG1);
+ val |= USB2PHY_DISCON_BYP_LATCH;
+ omap_usb_writel(phy->phy_base, USB2PHY_ANA_CONFIG1, val);
+ }
+
+ 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,
+};
+
+static const struct usb_phy_data omap_usb2_data = {
+ .label = "omap_usb2",
+ .flags = OMAP_USB2_HAS_START_SRP | OMAP_USB2_HAS_SET_VBUS,
+ .mask = OMAP_DEV_PHY_PD,
+ .power_off = OMAP_DEV_PHY_PD,
+};
+
+static const struct usb_phy_data omap5_usb2_data = {
+ .label = "omap5_usb2",
+ .flags = 0,
+ .mask = OMAP_DEV_PHY_PD,
+ .power_off = OMAP_DEV_PHY_PD,
+};
+
+static const struct usb_phy_data dra7x_usb2_data = {
+ .label = "dra7x_usb2",
+ .flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT,
+ .mask = OMAP_DEV_PHY_PD,
+ .power_off = OMAP_DEV_PHY_PD,
+};
+
+static const struct usb_phy_data dra7x_usb2_phy2_data = {
+ .label = "dra7x_usb2_phy2",
+ .flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT,
+ .mask = OMAP_USB2_PHY_PD,
+ .power_off = OMAP_USB2_PHY_PD,
+};
+
+static const struct usb_phy_data am437x_usb2_data = {
+ .label = "am437x_usb2",
+ .flags = 0,
+ .mask = AM437X_USB2_PHY_PD | AM437X_USB2_OTG_PD |
+ AM437X_USB2_OTGVDET_EN | AM437X_USB2_OTGSESSEND_EN,
+ .power_on = AM437X_USB2_OTGVDET_EN | AM437X_USB2_OTGSESSEND_EN,
+ .power_off = AM437X_USB2_PHY_PD | AM437X_USB2_OTG_PD,
+};
+
+static const struct of_device_id omap_usb2_id_table[] = {
+ {
+ .compatible = "ti,omap-usb2",
+ .data = &omap_usb2_data,
+ },
+ {
+ .compatible = "ti,omap5-usb2",
+ .data = &omap5_usb2_data,
+ },
+ {
+ .compatible = "ti,dra7x-usb2",
+ .data = &dra7x_usb2_data,
+ },
+ {
+ .compatible = "ti,dra7x-usb2-phy2",
+ .data = &dra7x_usb2_phy2_data,
+ },
+ {
+ .compatible = "ti,am437x-usb2",
+ .data = &am437x_usb2_data,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, omap_usb2_id_table);
+
+static int omap_usb2_probe(struct platform_device *pdev)
+{
+ struct omap_usb *phy;
+ struct phy *generic_phy;
+ struct resource *res;
+ struct phy_provider *phy_provider;
+ struct usb_otg *otg;
+ struct device_node *node = pdev->dev.of_node;
+ struct device_node *control_node;
+ struct platform_device *control_pdev;
+ const struct of_device_id *of_id;
+ struct usb_phy_data *phy_data;
+
+ of_id = of_match_device(omap_usb2_id_table, &pdev->dev);
+
+ if (!of_id)
+ return -EINVAL;
+
+ phy_data = (struct usb_phy_data *)of_id->data;
+
+ phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL);
+ if (!otg)
+ return -ENOMEM;
+
+ phy->dev = &pdev->dev;
+
+ phy->phy.dev = phy->dev;
+ phy->phy.label = phy_data->label;
+ phy->phy.otg = otg;
+ phy->phy.type = USB_PHY_TYPE_USB2;
+ phy->mask = phy_data->mask;
+ phy->power_on = phy_data->power_on;
+ phy->power_off = phy_data->power_off;
+
+ if (phy_data->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ phy->phy_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(phy->phy_base))
+ return PTR_ERR(phy->phy_base);
+ phy->flags |= OMAP_USB2_CALIBRATE_FALSE_DISCONNECT;
+ }
+
+ phy->syscon_phy_power = syscon_regmap_lookup_by_phandle(node,
+ "syscon-phy-power");
+ if (IS_ERR(phy->syscon_phy_power)) {
+ dev_dbg(&pdev->dev,
+ "can't get syscon-phy-power, using control device\n");
+ phy->syscon_phy_power = NULL;
+
+ control_node = of_parse_phandle(node, "ctrl-module", 0);
+ if (!control_node) {
+ dev_err(&pdev->dev,
+ "Failed to get control device phandle\n");
+ return -EINVAL;
+ }
+
+ control_pdev = of_find_device_by_node(control_node);
+ if (!control_pdev) {
+ dev_err(&pdev->dev, "Failed to get control device\n");
+ return -EINVAL;
+ }
+ phy->control_dev = &control_pdev->dev;
+ } else {
+ if (of_property_read_u32_index(node,
+ "syscon-phy-power", 1,
+ &phy->power_reg)) {
+ dev_err(&pdev->dev,
+ "couldn't get power reg. offset\n");
+ return -EINVAL;
+ }
+ }
+
+ otg->set_host = omap_usb_set_host;
+ otg->set_peripheral = omap_usb_set_peripheral;
+ if (phy_data->flags & OMAP_USB2_HAS_SET_VBUS)
+ otg->set_vbus = omap_usb_set_vbus;
+ if (phy_data->flags & OMAP_USB2_HAS_START_SRP)
+ otg->start_srp = omap_usb_start_srp;
+ otg->usb_phy = &phy->phy;
+
+ platform_set_drvdata(pdev, phy);
+ pm_runtime_enable(phy->dev);
+
+ generic_phy = devm_phy_create(phy->dev, NULL, &ops);
+ if (IS_ERR(generic_phy)) {
+ pm_runtime_disable(phy->dev);
+ return PTR_ERR(generic_phy);
+ }
+
+ phy_set_drvdata(generic_phy, phy);
+ omap_usb_power_off(generic_phy);
+
+ phy_provider = devm_of_phy_provider_register(phy->dev,
+ of_phy_simple_xlate);
+ if (IS_ERR(phy_provider)) {
+ pm_runtime_disable(phy->dev);
+ return PTR_ERR(phy_provider);
+ }
+
+ phy->wkupclk = devm_clk_get(phy->dev, "wkupclk");
+ if (IS_ERR(phy->wkupclk)) {
+ dev_warn(&pdev->dev, "unable to get wkupclk, trying old name\n");
+ phy->wkupclk = devm_clk_get(phy->dev, "usb_phy_cm_clk32k");
+ if (IS_ERR(phy->wkupclk)) {
+ dev_err(&pdev->dev, "unable to get usb_phy_cm_clk32k\n");
+ pm_runtime_disable(phy->dev);
+ return PTR_ERR(phy->wkupclk);
+ } else {
+ dev_warn(&pdev->dev,
+ "found usb_phy_cm_clk32k, please fix DTS\n");
+ }
+ }
+ clk_prepare(phy->wkupclk);
+
+ phy->optclk = devm_clk_get(phy->dev, "refclk");
+ if (IS_ERR(phy->optclk)) {
+ dev_dbg(&pdev->dev, "unable to get refclk, trying old name\n");
+ phy->optclk = devm_clk_get(phy->dev, "usb_otg_ss_refclk960m");
+ if (IS_ERR(phy->optclk)) {
+ dev_dbg(&pdev->dev,
+ "unable to get usb_otg_ss_refclk960m\n");
+ } else {
+ dev_warn(&pdev->dev,
+ "found usb_otg_ss_refclk960m, please fix DTS\n");
+ }
+ }
+
+ if (!IS_ERR(phy->optclk))
+ clk_prepare(phy->optclk);
+
+ usb_add_phy_dev(&phy->phy);
+
+ return 0;
+}
+
+static int omap_usb2_remove(struct platform_device *pdev)
+{
+ struct omap_usb *phy = platform_get_drvdata(pdev);
+
+ clk_unprepare(phy->wkupclk);
+ if (!IS_ERR(phy->optclk))
+ clk_unprepare(phy->optclk);
+ usb_remove_phy(&phy->phy);
+ pm_runtime_disable(phy->dev);
+
+ return 0;
+}
+
+static struct platform_driver omap_usb2_driver = {
+ .probe = omap_usb2_probe,
+ .remove = omap_usb2_remove,
+ .driver = {
+ .name = "omap-usb2",
+ .of_match_table = omap_usb2_id_table,
+ },
+};
+
+module_platform_driver(omap_usb2_driver);
+
+MODULE_ALIAS("platform:omap_usb2");
+MODULE_AUTHOR("Texas Instruments Inc.");
+MODULE_DESCRIPTION("OMAP USB2 phy driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/ti/phy-ti-pipe3.c b/drivers/phy/ti/phy-ti-pipe3.c
new file mode 100644
index 000000000..693acc167
--- /dev/null
+++ b/drivers/phy/ti/phy-ti-pipe3.c
@@ -0,0 +1,796 @@
+/*
+ * phy-ti-pipe3 - PIPE3 PHY driver.
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
+ * 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.
+ *
+ * Author: Kishon Vijay Abraham I <kishon@ti.com>
+ *
+ * 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/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/phy/phy.h>
+#include <linux/of.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/pm_runtime.h>
+#include <linux/delay.h>
+#include <linux/phy/omap_control_phy.h>
+#include <linux/of_platform.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#define PLL_STATUS 0x00000004
+#define PLL_GO 0x00000008
+#define PLL_CONFIGURATION1 0x0000000C
+#define PLL_CONFIGURATION2 0x00000010
+#define PLL_CONFIGURATION3 0x00000014
+#define PLL_CONFIGURATION4 0x00000020
+
+#define PLL_REGM_MASK 0x001FFE00
+#define PLL_REGM_SHIFT 0x9
+#define PLL_REGM_F_MASK 0x0003FFFF
+#define PLL_REGM_F_SHIFT 0x0
+#define PLL_REGN_MASK 0x000001FE
+#define PLL_REGN_SHIFT 0x1
+#define PLL_SELFREQDCO_MASK 0x0000000E
+#define PLL_SELFREQDCO_SHIFT 0x1
+#define PLL_SD_MASK 0x0003FC00
+#define PLL_SD_SHIFT 10
+#define SET_PLL_GO 0x1
+#define PLL_LDOPWDN BIT(15)
+#define PLL_TICOPWDN BIT(16)
+#define PLL_LOCK 0x2
+#define PLL_IDLE 0x1
+
+#define SATA_PLL_SOFT_RESET BIT(18)
+
+#define PIPE3_PHY_PWRCTL_CLK_CMD_MASK 0x003FC000
+#define PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT 14
+
+#define PIPE3_PHY_PWRCTL_CLK_FREQ_MASK 0xFFC00000
+#define PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT 22
+
+#define PIPE3_PHY_TX_RX_POWERON 0x3
+#define PIPE3_PHY_TX_RX_POWEROFF 0x0
+
+#define PCIE_PCS_MASK 0xFF0000
+#define PCIE_PCS_DELAY_COUNT_SHIFT 0x10
+
+#define PCIEPHYRX_ANA_PROGRAMMABILITY 0x0000000C
+#define INTERFACE_MASK GENMASK(31, 27)
+#define INTERFACE_SHIFT 27
+#define LOSD_MASK GENMASK(17, 14)
+#define LOSD_SHIFT 14
+#define MEM_PLLDIV GENMASK(6, 5)
+
+#define PCIEPHYRX_TRIM 0x0000001C
+#define MEM_DLL_TRIM_SEL GENMASK(31, 30)
+#define MEM_DLL_TRIM_SHIFT 30
+
+#define PCIEPHYRX_DLL 0x00000024
+#define MEM_DLL_PHINT_RATE GENMASK(31, 30)
+
+#define PCIEPHYRX_DIGITAL_MODES 0x00000028
+#define MEM_CDR_FASTLOCK BIT(23)
+#define MEM_CDR_LBW GENMASK(22, 21)
+#define MEM_CDR_STEPCNT GENMASK(20, 19)
+#define MEM_CDR_STL_MASK GENMASK(18, 16)
+#define MEM_CDR_STL_SHIFT 16
+#define MEM_CDR_THR_MASK GENMASK(15, 13)
+#define MEM_CDR_THR_SHIFT 13
+#define MEM_CDR_THR_MODE BIT(12)
+#define MEM_CDR_CDR_2NDO_SDM_MODE BIT(11)
+#define MEM_OVRD_HS_RATE BIT(26)
+
+#define PCIEPHYRX_EQUALIZER 0x00000038
+#define MEM_EQLEV GENMASK(31, 16)
+#define MEM_EQFTC GENMASK(15, 11)
+#define MEM_EQCTL GENMASK(10, 7)
+#define MEM_EQCTL_SHIFT 7
+#define MEM_OVRD_EQLEV BIT(2)
+#define MEM_OVRD_EQFTC BIT(1)
+
+/*
+ * This is an Empirical value that works, need to confirm the actual
+ * value required for the PIPE3PHY_PLL_CONFIGURATION2.PLL_IDLE status
+ * to be correctly reflected in the PIPE3PHY_PLL_STATUS register.
+ */
+#define PLL_IDLE_TIME 100 /* in milliseconds */
+#define PLL_LOCK_TIME 100 /* in milliseconds */
+
+struct pipe3_dpll_params {
+ u16 m;
+ u8 n;
+ u8 freq:3;
+ u8 sd;
+ u32 mf;
+};
+
+struct pipe3_dpll_map {
+ unsigned long rate;
+ struct pipe3_dpll_params params;
+};
+
+struct ti_pipe3 {
+ void __iomem *pll_ctrl_base;
+ void __iomem *phy_rx;
+ void __iomem *phy_tx;
+ struct device *dev;
+ struct device *control_dev;
+ struct clk *wkupclk;
+ struct clk *sys_clk;
+ struct clk *refclk;
+ struct clk *div_clk;
+ struct pipe3_dpll_map *dpll_map;
+ struct regmap *phy_power_syscon; /* ctrl. reg. acces */
+ struct regmap *pcs_syscon; /* ctrl. reg. acces */
+ struct regmap *dpll_reset_syscon; /* ctrl. reg. acces */
+ unsigned int dpll_reset_reg; /* reg. index within syscon */
+ unsigned int power_reg; /* power reg. index within syscon */
+ unsigned int pcie_pcs_reg; /* pcs reg. index in syscon */
+ bool sata_refclk_enabled;
+};
+
+static struct pipe3_dpll_map dpll_map_usb[] = {
+ {12000000, {1250, 5, 4, 20, 0} }, /* 12 MHz */
+ {16800000, {3125, 20, 4, 20, 0} }, /* 16.8 MHz */
+ {19200000, {1172, 8, 4, 20, 65537} }, /* 19.2 MHz */
+ {20000000, {1000, 7, 4, 10, 0} }, /* 20 MHz */
+ {26000000, {1250, 12, 4, 20, 0} }, /* 26 MHz */
+ {38400000, {3125, 47, 4, 20, 92843} }, /* 38.4 MHz */
+ { }, /* Terminator */
+};
+
+static struct pipe3_dpll_map dpll_map_sata[] = {
+ {12000000, {625, 4, 4, 6, 0} }, /* 12 MHz */
+ {16800000, {625, 6, 4, 7, 0} }, /* 16.8 MHz */
+ {19200000, {625, 7, 4, 6, 0} }, /* 19.2 MHz */
+ {20000000, {750, 9, 4, 6, 0} }, /* 20 MHz */
+ {26000000, {750, 12, 4, 6, 0} }, /* 26 MHz */
+ {38400000, {625, 15, 4, 6, 0} }, /* 38.4 MHz */
+ { }, /* Terminator */
+};
+
+static inline u32 ti_pipe3_readl(void __iomem *addr, unsigned offset)
+{
+ return __raw_readl(addr + offset);
+}
+
+static inline void ti_pipe3_writel(void __iomem *addr, unsigned offset,
+ u32 data)
+{
+ __raw_writel(data, addr + offset);
+}
+
+static struct pipe3_dpll_params *ti_pipe3_get_dpll_params(struct ti_pipe3 *phy)
+{
+ unsigned long rate;
+ struct pipe3_dpll_map *dpll_map = phy->dpll_map;
+
+ rate = clk_get_rate(phy->sys_clk);
+
+ for (; dpll_map->rate; dpll_map++) {
+ if (rate == dpll_map->rate)
+ return &dpll_map->params;
+ }
+
+ dev_err(phy->dev, "No DPLL configuration for %lu Hz SYS CLK\n", rate);
+
+ return NULL;
+}
+
+static int ti_pipe3_enable_clocks(struct ti_pipe3 *phy);
+static void ti_pipe3_disable_clocks(struct ti_pipe3 *phy);
+
+static int ti_pipe3_power_off(struct phy *x)
+{
+ u32 val;
+ int ret;
+ struct ti_pipe3 *phy = phy_get_drvdata(x);
+
+ if (!phy->phy_power_syscon) {
+ omap_control_phy_power(phy->control_dev, 0);
+ return 0;
+ }
+
+ val = PIPE3_PHY_TX_RX_POWEROFF << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT;
+
+ ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg,
+ PIPE3_PHY_PWRCTL_CLK_CMD_MASK, val);
+ return ret;
+}
+
+static int ti_pipe3_power_on(struct phy *x)
+{
+ u32 val;
+ u32 mask;
+ int ret;
+ unsigned long rate;
+ struct ti_pipe3 *phy = phy_get_drvdata(x);
+
+ if (!phy->phy_power_syscon) {
+ omap_control_phy_power(phy->control_dev, 1);
+ return 0;
+ }
+
+ rate = clk_get_rate(phy->sys_clk);
+ if (!rate) {
+ dev_err(phy->dev, "Invalid clock rate\n");
+ return -EINVAL;
+ }
+ rate = rate / 1000000;
+ mask = OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK |
+ OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK;
+ val = PIPE3_PHY_TX_RX_POWERON << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT;
+ val |= rate << OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT;
+
+ ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg,
+ mask, val);
+ return ret;
+}
+
+static int ti_pipe3_dpll_wait_lock(struct ti_pipe3 *phy)
+{
+ u32 val;
+ unsigned long timeout;
+
+ timeout = jiffies + msecs_to_jiffies(PLL_LOCK_TIME);
+ do {
+ cpu_relax();
+ val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS);
+ if (val & PLL_LOCK)
+ return 0;
+ } while (!time_after(jiffies, timeout));
+
+ dev_err(phy->dev, "DPLL failed to lock\n");
+ return -EBUSY;
+}
+
+static int ti_pipe3_dpll_program(struct ti_pipe3 *phy)
+{
+ u32 val;
+ struct pipe3_dpll_params *dpll_params;
+
+ dpll_params = ti_pipe3_get_dpll_params(phy);
+ if (!dpll_params)
+ return -EINVAL;
+
+ val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION1);
+ val &= ~PLL_REGN_MASK;
+ val |= dpll_params->n << PLL_REGN_SHIFT;
+ ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION1, val);
+
+ val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2);
+ val &= ~PLL_SELFREQDCO_MASK;
+ val |= dpll_params->freq << PLL_SELFREQDCO_SHIFT;
+ ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val);
+
+ val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION1);
+ val &= ~PLL_REGM_MASK;
+ val |= dpll_params->m << PLL_REGM_SHIFT;
+ ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION1, val);
+
+ val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION4);
+ val &= ~PLL_REGM_F_MASK;
+ val |= dpll_params->mf << PLL_REGM_F_SHIFT;
+ ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION4, val);
+
+ val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION3);
+ val &= ~PLL_SD_MASK;
+ val |= dpll_params->sd << PLL_SD_SHIFT;
+ ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION3, val);
+
+ ti_pipe3_writel(phy->pll_ctrl_base, PLL_GO, SET_PLL_GO);
+
+ return ti_pipe3_dpll_wait_lock(phy);
+}
+
+static void ti_pipe3_calibrate(struct ti_pipe3 *phy)
+{
+ u32 val;
+
+ val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_ANA_PROGRAMMABILITY);
+ val &= ~(INTERFACE_MASK | LOSD_MASK | MEM_PLLDIV);
+ val |= (0x1 << INTERFACE_SHIFT | 0xA << LOSD_SHIFT);
+ ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_ANA_PROGRAMMABILITY, val);
+
+ val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_DIGITAL_MODES);
+ val &= ~(MEM_CDR_STEPCNT | MEM_CDR_STL_MASK | MEM_CDR_THR_MASK |
+ MEM_CDR_CDR_2NDO_SDM_MODE | MEM_OVRD_HS_RATE);
+ val |= (MEM_CDR_FASTLOCK | MEM_CDR_LBW | 0x3 << MEM_CDR_STL_SHIFT |
+ 0x1 << MEM_CDR_THR_SHIFT | MEM_CDR_THR_MODE);
+ ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_DIGITAL_MODES, val);
+
+ val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_TRIM);
+ val &= ~MEM_DLL_TRIM_SEL;
+ val |= 0x2 << MEM_DLL_TRIM_SHIFT;
+ ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_TRIM, val);
+
+ val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_DLL);
+ val |= MEM_DLL_PHINT_RATE;
+ ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_DLL, val);
+
+ val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_EQUALIZER);
+ val &= ~(MEM_EQLEV | MEM_EQCTL | MEM_OVRD_EQLEV | MEM_OVRD_EQFTC);
+ val |= MEM_EQFTC | 0x1 << MEM_EQCTL_SHIFT;
+ ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_EQUALIZER, val);
+}
+
+static int ti_pipe3_init(struct phy *x)
+{
+ struct ti_pipe3 *phy = phy_get_drvdata(x);
+ u32 val;
+ int ret = 0;
+
+ ti_pipe3_enable_clocks(phy);
+ /*
+ * Set pcie_pcs register to 0x96 for proper functioning of phy
+ * as recommended in AM572x TRM SPRUHZ6, section 18.5.2.2, table
+ * 18-1804.
+ */
+ if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie")) {
+ if (!phy->pcs_syscon) {
+ omap_control_pcie_pcs(phy->control_dev, 0x96);
+ return 0;
+ }
+
+ val = 0x96 << OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT;
+ ret = regmap_update_bits(phy->pcs_syscon, phy->pcie_pcs_reg,
+ PCIE_PCS_MASK, val);
+ if (ret)
+ return ret;
+
+ ti_pipe3_calibrate(phy);
+
+ return 0;
+ }
+
+ /* Bring it out of IDLE if it is IDLE */
+ val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2);
+ if (val & PLL_IDLE) {
+ val &= ~PLL_IDLE;
+ ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val);
+ ret = ti_pipe3_dpll_wait_lock(phy);
+ }
+
+ /* SATA has issues if re-programmed when locked */
+ val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS);
+ if ((val & PLL_LOCK) && of_device_is_compatible(phy->dev->of_node,
+ "ti,phy-pipe3-sata"))
+ return ret;
+
+ /* Program the DPLL */
+ ret = ti_pipe3_dpll_program(phy);
+ if (ret) {
+ ti_pipe3_disable_clocks(phy);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int ti_pipe3_exit(struct phy *x)
+{
+ struct ti_pipe3 *phy = phy_get_drvdata(x);
+ u32 val;
+ unsigned long timeout;
+
+ /* If dpll_reset_syscon is not present we wont power down SATA DPLL
+ * due to Errata i783
+ */
+ if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata") &&
+ !phy->dpll_reset_syscon)
+ return 0;
+
+ /* PCIe doesn't have internal DPLL */
+ if (!of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie")) {
+ /* Put DPLL in IDLE mode */
+ val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2);
+ val |= PLL_IDLE;
+ ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val);
+
+ /* wait for LDO and Oscillator to power down */
+ timeout = jiffies + msecs_to_jiffies(PLL_IDLE_TIME);
+ do {
+ cpu_relax();
+ val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS);
+ if ((val & PLL_TICOPWDN) && (val & PLL_LDOPWDN))
+ break;
+ } while (!time_after(jiffies, timeout));
+
+ if (!(val & PLL_TICOPWDN) || !(val & PLL_LDOPWDN)) {
+ dev_err(phy->dev, "Failed to power down: PLL_STATUS 0x%x\n",
+ val);
+ return -EBUSY;
+ }
+ }
+
+ /* i783: SATA needs control bit toggle after PLL unlock */
+ if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata")) {
+ regmap_update_bits(phy->dpll_reset_syscon, phy->dpll_reset_reg,
+ SATA_PLL_SOFT_RESET, SATA_PLL_SOFT_RESET);
+ regmap_update_bits(phy->dpll_reset_syscon, phy->dpll_reset_reg,
+ SATA_PLL_SOFT_RESET, 0);
+ }
+
+ ti_pipe3_disable_clocks(phy);
+
+ return 0;
+}
+static const struct phy_ops ops = {
+ .init = ti_pipe3_init,
+ .exit = ti_pipe3_exit,
+ .power_on = ti_pipe3_power_on,
+ .power_off = ti_pipe3_power_off,
+ .owner = THIS_MODULE,
+};
+
+static const struct of_device_id ti_pipe3_id_table[];
+
+static int ti_pipe3_get_clk(struct ti_pipe3 *phy)
+{
+ struct clk *clk;
+ struct device *dev = phy->dev;
+ struct device_node *node = dev->of_node;
+
+ phy->refclk = devm_clk_get(dev, "refclk");
+ if (IS_ERR(phy->refclk)) {
+ dev_err(dev, "unable to get refclk\n");
+ /* older DTBs have missing refclk in SATA PHY
+ * so don't bail out in case of SATA PHY.
+ */
+ if (!of_device_is_compatible(node, "ti,phy-pipe3-sata"))
+ return PTR_ERR(phy->refclk);
+ }
+
+ if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) {
+ phy->wkupclk = devm_clk_get(dev, "wkupclk");
+ if (IS_ERR(phy->wkupclk)) {
+ dev_err(dev, "unable to get wkupclk\n");
+ return PTR_ERR(phy->wkupclk);
+ }
+ } else {
+ phy->wkupclk = ERR_PTR(-ENODEV);
+ }
+
+ if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie") ||
+ phy->phy_power_syscon) {
+ phy->sys_clk = devm_clk_get(dev, "sysclk");
+ if (IS_ERR(phy->sys_clk)) {
+ dev_err(dev, "unable to get sysclk\n");
+ return -EINVAL;
+ }
+ }
+
+ if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) {
+ clk = devm_clk_get(dev, "dpll_ref");
+ if (IS_ERR(clk)) {
+ dev_err(dev, "unable to get dpll ref clk\n");
+ return PTR_ERR(clk);
+ }
+ clk_set_rate(clk, 1500000000);
+
+ clk = devm_clk_get(dev, "dpll_ref_m2");
+ if (IS_ERR(clk)) {
+ dev_err(dev, "unable to get dpll ref m2 clk\n");
+ return PTR_ERR(clk);
+ }
+ clk_set_rate(clk, 100000000);
+
+ clk = devm_clk_get(dev, "phy-div");
+ if (IS_ERR(clk)) {
+ dev_err(dev, "unable to get phy-div clk\n");
+ return PTR_ERR(clk);
+ }
+ clk_set_rate(clk, 100000000);
+
+ phy->div_clk = devm_clk_get(dev, "div-clk");
+ if (IS_ERR(phy->div_clk)) {
+ dev_err(dev, "unable to get div-clk\n");
+ return PTR_ERR(phy->div_clk);
+ }
+ } else {
+ phy->div_clk = ERR_PTR(-ENODEV);
+ }
+
+ return 0;
+}
+
+static int ti_pipe3_get_sysctrl(struct ti_pipe3 *phy)
+{
+ struct device *dev = phy->dev;
+ struct device_node *node = dev->of_node;
+ struct device_node *control_node;
+ struct platform_device *control_pdev;
+
+ phy->phy_power_syscon = syscon_regmap_lookup_by_phandle(node,
+ "syscon-phy-power");
+ if (IS_ERR(phy->phy_power_syscon)) {
+ dev_dbg(dev,
+ "can't get syscon-phy-power, using control device\n");
+ phy->phy_power_syscon = NULL;
+ } else {
+ if (of_property_read_u32_index(node,
+ "syscon-phy-power", 1,
+ &phy->power_reg)) {
+ dev_err(dev, "couldn't get power reg. offset\n");
+ return -EINVAL;
+ }
+ }
+
+ if (!phy->phy_power_syscon) {
+ control_node = of_parse_phandle(node, "ctrl-module", 0);
+ if (!control_node) {
+ dev_err(dev, "Failed to get control device phandle\n");
+ return -EINVAL;
+ }
+
+ control_pdev = of_find_device_by_node(control_node);
+ if (!control_pdev) {
+ dev_err(dev, "Failed to get control device\n");
+ return -EINVAL;
+ }
+
+ phy->control_dev = &control_pdev->dev;
+ }
+
+ if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) {
+ phy->pcs_syscon = syscon_regmap_lookup_by_phandle(node,
+ "syscon-pcs");
+ if (IS_ERR(phy->pcs_syscon)) {
+ dev_dbg(dev,
+ "can't get syscon-pcs, using omap control\n");
+ phy->pcs_syscon = NULL;
+ } else {
+ if (of_property_read_u32_index(node,
+ "syscon-pcs", 1,
+ &phy->pcie_pcs_reg)) {
+ dev_err(dev,
+ "couldn't get pcie pcs reg. offset\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) {
+ phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node,
+ "syscon-pllreset");
+ if (IS_ERR(phy->dpll_reset_syscon)) {
+ dev_info(dev,
+ "can't get syscon-pllreset, sata dpll won't idle\n");
+ phy->dpll_reset_syscon = NULL;
+ } else {
+ if (of_property_read_u32_index(node,
+ "syscon-pllreset", 1,
+ &phy->dpll_reset_reg)) {
+ dev_err(dev,
+ "couldn't get pllreset reg. offset\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int ti_pipe3_get_tx_rx_base(struct ti_pipe3 *phy)
+{
+ struct resource *res;
+ struct device *dev = phy->dev;
+ struct device_node *node = dev->of_node;
+ struct platform_device *pdev = to_platform_device(dev);
+
+ if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie"))
+ return 0;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "phy_rx");
+ phy->phy_rx = devm_ioremap_resource(dev, res);
+ if (IS_ERR(phy->phy_rx))
+ return PTR_ERR(phy->phy_rx);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "phy_tx");
+ phy->phy_tx = devm_ioremap_resource(dev, res);
+
+ return PTR_ERR_OR_ZERO(phy->phy_tx);
+}
+
+static int ti_pipe3_get_pll_base(struct ti_pipe3 *phy)
+{
+ struct resource *res;
+ const struct of_device_id *match;
+ struct device *dev = phy->dev;
+ struct device_node *node = dev->of_node;
+ struct platform_device *pdev = to_platform_device(dev);
+
+ if (of_device_is_compatible(node, "ti,phy-pipe3-pcie"))
+ return 0;
+
+ match = of_match_device(ti_pipe3_id_table, dev);
+ if (!match)
+ return -EINVAL;
+
+ phy->dpll_map = (struct pipe3_dpll_map *)match->data;
+ if (!phy->dpll_map) {
+ dev_err(dev, "no DPLL data\n");
+ return -EINVAL;
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "pll_ctrl");
+ phy->pll_ctrl_base = devm_ioremap_resource(dev, res);
+ return PTR_ERR_OR_ZERO(phy->pll_ctrl_base);
+}
+
+static int ti_pipe3_probe(struct platform_device *pdev)
+{
+ struct ti_pipe3 *phy;
+ struct phy *generic_phy;
+ struct phy_provider *phy_provider;
+ struct device_node *node = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ phy->dev = dev;
+
+ ret = ti_pipe3_get_pll_base(phy);
+ if (ret)
+ return ret;
+
+ ret = ti_pipe3_get_tx_rx_base(phy);
+ if (ret)
+ return ret;
+
+ ret = ti_pipe3_get_sysctrl(phy);
+ if (ret)
+ return ret;
+
+ ret = ti_pipe3_get_clk(phy);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, phy);
+ pm_runtime_enable(dev);
+
+ /*
+ * Prevent auto-disable of refclk for SATA PHY due to Errata i783
+ */
+ if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) {
+ if (!IS_ERR(phy->refclk)) {
+ clk_prepare_enable(phy->refclk);
+ phy->sata_refclk_enabled = true;
+ }
+ }
+
+ generic_phy = devm_phy_create(dev, NULL, &ops);
+ if (IS_ERR(generic_phy))
+ return PTR_ERR(generic_phy);
+
+ phy_set_drvdata(generic_phy, phy);
+
+ ti_pipe3_power_off(generic_phy);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static int ti_pipe3_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static int ti_pipe3_enable_clocks(struct ti_pipe3 *phy)
+{
+ int ret = 0;
+
+ if (!IS_ERR(phy->refclk)) {
+ ret = clk_prepare_enable(phy->refclk);
+ if (ret) {
+ dev_err(phy->dev, "Failed to enable refclk %d\n", ret);
+ return ret;
+ }
+ }
+
+ if (!IS_ERR(phy->wkupclk)) {
+ ret = clk_prepare_enable(phy->wkupclk);
+ if (ret) {
+ dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret);
+ goto disable_refclk;
+ }
+ }
+
+ if (!IS_ERR(phy->div_clk)) {
+ ret = clk_prepare_enable(phy->div_clk);
+ if (ret) {
+ dev_err(phy->dev, "Failed to enable div_clk %d\n", ret);
+ goto disable_wkupclk;
+ }
+ }
+
+ return 0;
+
+disable_wkupclk:
+ if (!IS_ERR(phy->wkupclk))
+ clk_disable_unprepare(phy->wkupclk);
+
+disable_refclk:
+ if (!IS_ERR(phy->refclk))
+ clk_disable_unprepare(phy->refclk);
+
+ return ret;
+}
+
+static void ti_pipe3_disable_clocks(struct ti_pipe3 *phy)
+{
+ if (!IS_ERR(phy->wkupclk))
+ clk_disable_unprepare(phy->wkupclk);
+ if (!IS_ERR(phy->refclk)) {
+ clk_disable_unprepare(phy->refclk);
+ /*
+ * SATA refclk needs an additional disable as we left it
+ * on in probe to avoid Errata i783
+ */
+ if (phy->sata_refclk_enabled) {
+ clk_disable_unprepare(phy->refclk);
+ phy->sata_refclk_enabled = false;
+ }
+ }
+
+ if (!IS_ERR(phy->div_clk))
+ clk_disable_unprepare(phy->div_clk);
+}
+
+static const struct of_device_id ti_pipe3_id_table[] = {
+ {
+ .compatible = "ti,phy-usb3",
+ .data = dpll_map_usb,
+ },
+ {
+ .compatible = "ti,omap-usb3",
+ .data = dpll_map_usb,
+ },
+ {
+ .compatible = "ti,phy-pipe3-sata",
+ .data = dpll_map_sata,
+ },
+ {
+ .compatible = "ti,phy-pipe3-pcie",
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ti_pipe3_id_table);
+
+static struct platform_driver ti_pipe3_driver = {
+ .probe = ti_pipe3_probe,
+ .remove = ti_pipe3_remove,
+ .driver = {
+ .name = "ti-pipe3",
+ .of_match_table = ti_pipe3_id_table,
+ },
+};
+
+module_platform_driver(ti_pipe3_driver);
+
+MODULE_ALIAS("platform:ti_pipe3");
+MODULE_AUTHOR("Texas Instruments Inc.");
+MODULE_DESCRIPTION("TI PIPE3 phy driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/ti/phy-tusb1210.c b/drivers/phy/ti/phy-tusb1210.c
new file mode 100644
index 000000000..b8ec39ac4
--- /dev/null
+++ b/drivers/phy/ti/phy-tusb1210.c
@@ -0,0 +1,182 @@
+/**
+ * tusb1210.c - TUSB1210 USB ULPI PHY driver
+ *
+ * Copyright (C) 2015 Intel Corporation
+ *
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ *
+ * 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/module.h>
+#include <linux/ulpi/driver.h>
+#include <linux/ulpi/regs.h>
+#include <linux/gpio/consumer.h>
+#include <linux/phy/ulpi_phy.h>
+
+#define TUSB1210_VENDOR_SPECIFIC2 0x80
+#define TUSB1210_VENDOR_SPECIFIC2_IHSTX_SHIFT 0
+#define TUSB1210_VENDOR_SPECIFIC2_ZHSDRV_SHIFT 4
+#define TUSB1210_VENDOR_SPECIFIC2_DP_SHIFT 6
+
+struct tusb1210 {
+ struct ulpi *ulpi;
+ struct phy *phy;
+ struct gpio_desc *gpio_reset;
+ struct gpio_desc *gpio_cs;
+ u8 vendor_specific2;
+};
+
+static int tusb1210_power_on(struct phy *phy)
+{
+ struct tusb1210 *tusb = phy_get_drvdata(phy);
+
+ gpiod_set_value_cansleep(tusb->gpio_reset, 1);
+ gpiod_set_value_cansleep(tusb->gpio_cs, 1);
+
+ /* Restore the optional eye diagram optimization value */
+ if (tusb->vendor_specific2)
+ ulpi_write(tusb->ulpi, TUSB1210_VENDOR_SPECIFIC2,
+ tusb->vendor_specific2);
+
+ return 0;
+}
+
+static int tusb1210_power_off(struct phy *phy)
+{
+ struct tusb1210 *tusb = phy_get_drvdata(phy);
+
+ gpiod_set_value_cansleep(tusb->gpio_reset, 0);
+ gpiod_set_value_cansleep(tusb->gpio_cs, 0);
+
+ return 0;
+}
+
+static int tusb1210_set_mode(struct phy *phy, enum phy_mode mode)
+{
+ struct tusb1210 *tusb = phy_get_drvdata(phy);
+ int ret;
+
+ ret = ulpi_read(tusb->ulpi, ULPI_OTG_CTRL);
+ if (ret < 0)
+ return ret;
+
+ switch (mode) {
+ case PHY_MODE_USB_HOST:
+ ret |= (ULPI_OTG_CTRL_DRVVBUS_EXT
+ | ULPI_OTG_CTRL_ID_PULLUP
+ | ULPI_OTG_CTRL_DP_PULLDOWN
+ | ULPI_OTG_CTRL_DM_PULLDOWN);
+ ulpi_write(tusb->ulpi, ULPI_OTG_CTRL, ret);
+ ret |= ULPI_OTG_CTRL_DRVVBUS;
+ break;
+ case PHY_MODE_USB_DEVICE:
+ ret &= ~(ULPI_OTG_CTRL_DRVVBUS
+ | ULPI_OTG_CTRL_DP_PULLDOWN
+ | ULPI_OTG_CTRL_DM_PULLDOWN);
+ ulpi_write(tusb->ulpi, ULPI_OTG_CTRL, ret);
+ ret &= ~ULPI_OTG_CTRL_DRVVBUS_EXT;
+ break;
+ default:
+ /* nothing */
+ return 0;
+ }
+
+ return ulpi_write(tusb->ulpi, ULPI_OTG_CTRL, ret);
+}
+
+static const struct phy_ops phy_ops = {
+ .power_on = tusb1210_power_on,
+ .power_off = tusb1210_power_off,
+ .set_mode = tusb1210_set_mode,
+ .owner = THIS_MODULE,
+};
+
+static int tusb1210_probe(struct ulpi *ulpi)
+{
+ struct tusb1210 *tusb;
+ u8 val, reg;
+
+ tusb = devm_kzalloc(&ulpi->dev, sizeof(*tusb), GFP_KERNEL);
+ if (!tusb)
+ return -ENOMEM;
+
+ tusb->gpio_reset = devm_gpiod_get_optional(&ulpi->dev, "reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(tusb->gpio_reset))
+ return PTR_ERR(tusb->gpio_reset);
+
+ gpiod_set_value_cansleep(tusb->gpio_reset, 1);
+
+ tusb->gpio_cs = devm_gpiod_get_optional(&ulpi->dev, "cs",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(tusb->gpio_cs))
+ return PTR_ERR(tusb->gpio_cs);
+
+ gpiod_set_value_cansleep(tusb->gpio_cs, 1);
+
+ /*
+ * VENDOR_SPECIFIC2 register in TUSB1210 can be used for configuring eye
+ * diagram optimization and DP/DM swap.
+ */
+
+ /* High speed output drive strength configuration */
+ device_property_read_u8(&ulpi->dev, "ihstx", &val);
+ reg = val << TUSB1210_VENDOR_SPECIFIC2_IHSTX_SHIFT;
+
+ /* High speed output impedance configuration */
+ device_property_read_u8(&ulpi->dev, "zhsdrv", &val);
+ reg |= val << TUSB1210_VENDOR_SPECIFIC2_ZHSDRV_SHIFT;
+
+ /* DP/DM swap control */
+ device_property_read_u8(&ulpi->dev, "datapolarity", &val);
+ reg |= val << TUSB1210_VENDOR_SPECIFIC2_DP_SHIFT;
+
+ if (reg) {
+ ulpi_write(ulpi, TUSB1210_VENDOR_SPECIFIC2, reg);
+ tusb->vendor_specific2 = reg;
+ }
+
+ tusb->phy = ulpi_phy_create(ulpi, &phy_ops);
+ if (IS_ERR(tusb->phy))
+ return PTR_ERR(tusb->phy);
+
+ tusb->ulpi = ulpi;
+
+ phy_set_drvdata(tusb->phy, tusb);
+ ulpi_set_drvdata(ulpi, tusb);
+ return 0;
+}
+
+static void tusb1210_remove(struct ulpi *ulpi)
+{
+ struct tusb1210 *tusb = ulpi_get_drvdata(ulpi);
+
+ ulpi_phy_destroy(ulpi, tusb->phy);
+}
+
+#define TI_VENDOR_ID 0x0451
+
+static const struct ulpi_device_id tusb1210_ulpi_id[] = {
+ { TI_VENDOR_ID, 0x1507, }, /* TUSB1210 */
+ { TI_VENDOR_ID, 0x1508, }, /* TUSB1211 */
+ { },
+};
+MODULE_DEVICE_TABLE(ulpi, tusb1210_ulpi_id);
+
+static struct ulpi_driver tusb1210_driver = {
+ .id_table = tusb1210_ulpi_id,
+ .probe = tusb1210_probe,
+ .remove = tusb1210_remove,
+ .driver = {
+ .name = "tusb1210",
+ .owner = THIS_MODULE,
+ },
+};
+
+module_ulpi_driver(tusb1210_driver);
+
+MODULE_AUTHOR("Intel Corporation");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("TUSB1210 ULPI PHY driver");
diff --git a/drivers/phy/ti/phy-twl4030-usb.c b/drivers/phy/ti/phy-twl4030-usb.c
new file mode 100644
index 000000000..ea7564392
--- /dev/null
+++ b/drivers/phy/ti/phy-twl4030-usb.c
@@ -0,0 +1,868 @@
+/*
+ * twl4030_usb - TWL4030 USB transceiver, talking to OMAP OTG controller
+ *
+ * Copyright (C) 2004-2007 Texas Instruments
+ * Copyright (C) 2008 Nokia Corporation
+ * Contact: Felipe Balbi <felipe.balbi@nokia.com>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Current status:
+ * - HS USB ULPI mode works.
+ * - 3-pin mode support may be added in future.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/usb/otg.h>
+#include <linux/phy/phy.h>
+#include <linux/pm_runtime.h>
+#include <linux/usb/musb.h>
+#include <linux/usb/ulpi.h>
+#include <linux/mfd/twl.h>
+#include <linux/regulator/consumer.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+
+/* Register defines */
+
+#define MCPC_CTRL 0x30
+#define MCPC_CTRL_RTSOL (1 << 7)
+#define MCPC_CTRL_EXTSWR (1 << 6)
+#define MCPC_CTRL_EXTSWC (1 << 5)
+#define MCPC_CTRL_VOICESW (1 << 4)
+#define MCPC_CTRL_OUT64K (1 << 3)
+#define MCPC_CTRL_RTSCTSSW (1 << 2)
+#define MCPC_CTRL_HS_UART (1 << 0)
+
+#define MCPC_IO_CTRL 0x33
+#define MCPC_IO_CTRL_MICBIASEN (1 << 5)
+#define MCPC_IO_CTRL_CTS_NPU (1 << 4)
+#define MCPC_IO_CTRL_RXD_PU (1 << 3)
+#define MCPC_IO_CTRL_TXDTYP (1 << 2)
+#define MCPC_IO_CTRL_CTSTYP (1 << 1)
+#define MCPC_IO_CTRL_RTSTYP (1 << 0)
+
+#define MCPC_CTRL2 0x36
+#define MCPC_CTRL2_MCPC_CK_EN (1 << 0)
+
+#define OTHER_FUNC_CTRL 0x80
+#define OTHER_FUNC_CTRL_BDIS_ACON_EN (1 << 4)
+#define OTHER_FUNC_CTRL_FIVEWIRE_MODE (1 << 2)
+
+#define OTHER_IFC_CTRL 0x83
+#define OTHER_IFC_CTRL_OE_INT_EN (1 << 6)
+#define OTHER_IFC_CTRL_CEA2011_MODE (1 << 5)
+#define OTHER_IFC_CTRL_FSLSSERIALMODE_4PIN (1 << 4)
+#define OTHER_IFC_CTRL_HIZ_ULPI_60MHZ_OUT (1 << 3)
+#define OTHER_IFC_CTRL_HIZ_ULPI (1 << 2)
+#define OTHER_IFC_CTRL_ALT_INT_REROUTE (1 << 0)
+
+#define OTHER_INT_EN_RISE 0x86
+#define OTHER_INT_EN_FALL 0x89
+#define OTHER_INT_STS 0x8C
+#define OTHER_INT_LATCH 0x8D
+#define OTHER_INT_VB_SESS_VLD (1 << 7)
+#define OTHER_INT_DM_HI (1 << 6) /* not valid for "latch" reg */
+#define OTHER_INT_DP_HI (1 << 5) /* not valid for "latch" reg */
+#define OTHER_INT_BDIS_ACON (1 << 3) /* not valid for "fall" regs */
+#define OTHER_INT_MANU (1 << 1)
+#define OTHER_INT_ABNORMAL_STRESS (1 << 0)
+
+#define ID_STATUS 0x96
+#define ID_RES_FLOAT (1 << 4)
+#define ID_RES_440K (1 << 3)
+#define ID_RES_200K (1 << 2)
+#define ID_RES_102K (1 << 1)
+#define ID_RES_GND (1 << 0)
+
+#define POWER_CTRL 0xAC
+#define POWER_CTRL_OTG_ENAB (1 << 5)
+
+#define OTHER_IFC_CTRL2 0xAF
+#define OTHER_IFC_CTRL2_ULPI_STP_LOW (1 << 4)
+#define OTHER_IFC_CTRL2_ULPI_TXEN_POL (1 << 3)
+#define OTHER_IFC_CTRL2_ULPI_4PIN_2430 (1 << 2)
+#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_MASK (3 << 0) /* bits 0 and 1 */
+#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT1N (0 << 0)
+#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT2N (1 << 0)
+
+#define REG_CTRL_EN 0xB2
+#define REG_CTRL_ERROR 0xB5
+#define ULPI_I2C_CONFLICT_INTEN (1 << 0)
+
+#define OTHER_FUNC_CTRL2 0xB8
+#define OTHER_FUNC_CTRL2_VBAT_TIMER_EN (1 << 0)
+
+/* following registers do not have separate _clr and _set registers */
+#define VBUS_DEBOUNCE 0xC0
+#define ID_DEBOUNCE 0xC1
+#define VBAT_TIMER 0xD3
+#define PHY_PWR_CTRL 0xFD
+#define PHY_PWR_PHYPWD (1 << 0)
+#define PHY_CLK_CTRL 0xFE
+#define PHY_CLK_CTRL_CLOCKGATING_EN (1 << 2)
+#define PHY_CLK_CTRL_CLK32K_EN (1 << 1)
+#define REQ_PHY_DPLL_CLK (1 << 0)
+#define PHY_CLK_CTRL_STS 0xFF
+#define PHY_DPLL_CLK (1 << 0)
+
+/* In module TWL_MODULE_PM_MASTER */
+#define STS_HW_CONDITIONS 0x0F
+
+/* In module TWL_MODULE_PM_RECEIVER */
+#define VUSB_DEDICATED1 0x7D
+#define VUSB_DEDICATED2 0x7E
+#define VUSB1V5_DEV_GRP 0x71
+#define VUSB1V5_TYPE 0x72
+#define VUSB1V5_REMAP 0x73
+#define VUSB1V8_DEV_GRP 0x74
+#define VUSB1V8_TYPE 0x75
+#define VUSB1V8_REMAP 0x76
+#define VUSB3V1_DEV_GRP 0x77
+#define VUSB3V1_TYPE 0x78
+#define VUSB3V1_REMAP 0x79
+
+/* In module TWL4030_MODULE_INTBR */
+#define PMBR1 0x0D
+#define GPIO_USB_4PIN_ULPI_2430C (3 << 0)
+
+static irqreturn_t twl4030_usb_irq(int irq, void *_twl);
+/*
+ * If VBUS is valid or ID is ground, then we know a
+ * cable is present and we need to be runtime-enabled
+ */
+static inline bool cable_present(enum musb_vbus_id_status stat)
+{
+ return stat == MUSB_VBUS_VALID ||
+ stat == MUSB_ID_GROUND;
+}
+
+struct twl4030_usb {
+ struct usb_phy phy;
+ struct device *dev;
+
+ /* TWL4030 internal USB regulator supplies */
+ struct regulator *usb1v5;
+ struct regulator *usb1v8;
+ struct regulator *usb3v1;
+
+ /* for vbus reporting with irqs disabled */
+ struct mutex lock;
+
+ /* pin configuration */
+ enum twl4030_usb_mode usb_mode;
+
+ int irq;
+ enum musb_vbus_id_status linkstat;
+ bool vbus_supplied;
+ bool musb_mailbox_pending;
+
+ struct delayed_work id_workaround_work;
+};
+
+/* internal define on top of container_of */
+#define phy_to_twl(x) container_of((x), struct twl4030_usb, phy)
+
+/*-------------------------------------------------------------------------*/
+
+static int twl4030_i2c_write_u8_verify(struct twl4030_usb *twl,
+ u8 module, u8 data, u8 address)
+{
+ u8 check = 0xFF;
+
+ if ((twl_i2c_write_u8(module, data, address) >= 0) &&
+ (twl_i2c_read_u8(module, &check, address) >= 0) &&
+ (check == data))
+ return 0;
+ dev_dbg(twl->dev, "Write%d[%d,0x%x] wrote %02x but read %02x\n",
+ 1, module, address, check, data);
+
+ /* Failed once: Try again */
+ if ((twl_i2c_write_u8(module, data, address) >= 0) &&
+ (twl_i2c_read_u8(module, &check, address) >= 0) &&
+ (check == data))
+ return 0;
+ dev_dbg(twl->dev, "Write%d[%d,0x%x] wrote %02x but read %02x\n",
+ 2, module, address, check, data);
+
+ /* Failed again: Return error */
+ return -EBUSY;
+}
+
+#define twl4030_usb_write_verify(twl, address, data) \
+ twl4030_i2c_write_u8_verify(twl, TWL_MODULE_USB, (data), (address))
+
+static inline int twl4030_usb_write(struct twl4030_usb *twl,
+ u8 address, u8 data)
+{
+ int ret = 0;
+
+ ret = twl_i2c_write_u8(TWL_MODULE_USB, data, address);
+ if (ret < 0)
+ dev_dbg(twl->dev,
+ "TWL4030:USB:Write[0x%x] Error %d\n", address, ret);
+ return ret;
+}
+
+static inline int twl4030_readb(struct twl4030_usb *twl, u8 module, u8 address)
+{
+ u8 data;
+ int ret = 0;
+
+ ret = twl_i2c_read_u8(module, &data, address);
+ if (ret >= 0)
+ ret = data;
+ else
+ dev_dbg(twl->dev,
+ "TWL4030:readb[0x%x,0x%x] Error %d\n",
+ module, address, ret);
+
+ return ret;
+}
+
+static inline int twl4030_usb_read(struct twl4030_usb *twl, u8 address)
+{
+ return twl4030_readb(twl, TWL_MODULE_USB, address);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static inline int
+twl4030_usb_set_bits(struct twl4030_usb *twl, u8 reg, u8 bits)
+{
+ return twl4030_usb_write(twl, ULPI_SET(reg), bits);
+}
+
+static inline int
+twl4030_usb_clear_bits(struct twl4030_usb *twl, u8 reg, u8 bits)
+{
+ return twl4030_usb_write(twl, ULPI_CLR(reg), bits);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static bool twl4030_is_driving_vbus(struct twl4030_usb *twl)
+{
+ int ret;
+
+ ret = twl4030_usb_read(twl, PHY_CLK_CTRL_STS);
+ if (ret < 0 || !(ret & PHY_DPLL_CLK))
+ /*
+ * if clocks are off, registers are not updated,
+ * but we can assume we don't drive VBUS in this case
+ */
+ return false;
+
+ ret = twl4030_usb_read(twl, ULPI_OTG_CTRL);
+ if (ret < 0)
+ return false;
+
+ return (ret & (ULPI_OTG_DRVVBUS | ULPI_OTG_CHRGVBUS)) ? true : false;
+}
+
+static enum musb_vbus_id_status
+ twl4030_usb_linkstat(struct twl4030_usb *twl)
+{
+ int status;
+ enum musb_vbus_id_status linkstat = MUSB_UNKNOWN;
+
+ twl->vbus_supplied = false;
+
+ /*
+ * For ID/VBUS sensing, see manual section 15.4.8 ...
+ * except when using only battery backup power, two
+ * comparators produce VBUS_PRES and ID_PRES signals,
+ * which don't match docs elsewhere. But ... BIT(7)
+ * and BIT(2) of STS_HW_CONDITIONS, respectively, do
+ * seem to match up. If either is true the USB_PRES
+ * signal is active, the OTG module is activated, and
+ * its interrupt may be raised (may wake the system).
+ */
+ status = twl4030_readb(twl, TWL_MODULE_PM_MASTER, STS_HW_CONDITIONS);
+ if (status < 0)
+ dev_err(twl->dev, "USB link status err %d\n", status);
+ else if (status & (BIT(7) | BIT(2))) {
+ if (status & BIT(7)) {
+ if (twl4030_is_driving_vbus(twl))
+ status &= ~BIT(7);
+ else
+ twl->vbus_supplied = true;
+ }
+
+ if (status & BIT(2))
+ linkstat = MUSB_ID_GROUND;
+ else if (status & BIT(7))
+ linkstat = MUSB_VBUS_VALID;
+ else
+ linkstat = MUSB_VBUS_OFF;
+ } else {
+ if (twl->linkstat != MUSB_UNKNOWN)
+ linkstat = MUSB_VBUS_OFF;
+ }
+
+ kobject_uevent(&twl->dev->kobj, linkstat == MUSB_VBUS_VALID
+ ? KOBJ_ONLINE : KOBJ_OFFLINE);
+
+ dev_dbg(twl->dev, "HW_CONDITIONS 0x%02x/%d; link %d\n",
+ status, status, linkstat);
+
+ /* REVISIT this assumes host and peripheral controllers
+ * are registered, and that both are active...
+ */
+
+ return linkstat;
+}
+
+static void twl4030_usb_set_mode(struct twl4030_usb *twl, int mode)
+{
+ twl->usb_mode = mode;
+
+ switch (mode) {
+ case T2_USB_MODE_ULPI:
+ twl4030_usb_clear_bits(twl, ULPI_IFC_CTRL,
+ ULPI_IFC_CTRL_CARKITMODE);
+ twl4030_usb_set_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB);
+ twl4030_usb_clear_bits(twl, ULPI_FUNC_CTRL,
+ ULPI_FUNC_CTRL_XCVRSEL_MASK |
+ ULPI_FUNC_CTRL_OPMODE_MASK);
+ break;
+ case -1:
+ /* FIXME: power on defaults */
+ break;
+ default:
+ dev_err(twl->dev, "unsupported T2 transceiver mode %d\n",
+ mode);
+ break;
+ }
+}
+
+static void twl4030_i2c_access(struct twl4030_usb *twl, int on)
+{
+ unsigned long timeout;
+ int val = twl4030_usb_read(twl, PHY_CLK_CTRL);
+
+ if (val >= 0) {
+ if (on) {
+ /* enable DPLL to access PHY registers over I2C */
+ val |= REQ_PHY_DPLL_CLK;
+ WARN_ON(twl4030_usb_write_verify(twl, PHY_CLK_CTRL,
+ (u8)val) < 0);
+
+ timeout = jiffies + HZ;
+ while (!(twl4030_usb_read(twl, PHY_CLK_CTRL_STS) &
+ PHY_DPLL_CLK)
+ && time_before(jiffies, timeout))
+ udelay(10);
+ if (!(twl4030_usb_read(twl, PHY_CLK_CTRL_STS) &
+ PHY_DPLL_CLK))
+ dev_err(twl->dev, "Timeout setting T2 HSUSB "
+ "PHY DPLL clock\n");
+ } else {
+ /* let ULPI control the DPLL clock */
+ val &= ~REQ_PHY_DPLL_CLK;
+ WARN_ON(twl4030_usb_write_verify(twl, PHY_CLK_CTRL,
+ (u8)val) < 0);
+ }
+ }
+}
+
+static void __twl4030_phy_power(struct twl4030_usb *twl, int on)
+{
+ u8 pwr = twl4030_usb_read(twl, PHY_PWR_CTRL);
+
+ if (on)
+ pwr &= ~PHY_PWR_PHYPWD;
+ else
+ pwr |= PHY_PWR_PHYPWD;
+
+ WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0);
+}
+
+static int __maybe_unused twl4030_usb_suspend(struct device *dev)
+{
+ struct twl4030_usb *twl = dev_get_drvdata(dev);
+
+ /*
+ * we need enabled runtime on resume,
+ * so turn irq off here, so we do not get it early
+ * note: wakeup on usb plug works independently of this
+ */
+ dev_dbg(twl->dev, "%s\n", __func__);
+ disable_irq(twl->irq);
+
+ return 0;
+}
+
+static int __maybe_unused twl4030_usb_resume(struct device *dev)
+{
+ struct twl4030_usb *twl = dev_get_drvdata(dev);
+
+ dev_dbg(twl->dev, "%s\n", __func__);
+ enable_irq(twl->irq);
+ /* check whether cable status changed */
+ twl4030_usb_irq(0, twl);
+
+ return 0;
+}
+
+static int __maybe_unused twl4030_usb_runtime_suspend(struct device *dev)
+{
+ struct twl4030_usb *twl = dev_get_drvdata(dev);
+
+ dev_dbg(twl->dev, "%s\n", __func__);
+
+ __twl4030_phy_power(twl, 0);
+ regulator_disable(twl->usb1v5);
+ regulator_disable(twl->usb1v8);
+ regulator_disable(twl->usb3v1);
+
+ return 0;
+}
+
+static int __maybe_unused twl4030_usb_runtime_resume(struct device *dev)
+{
+ struct twl4030_usb *twl = dev_get_drvdata(dev);
+ int res;
+
+ dev_dbg(twl->dev, "%s\n", __func__);
+
+ res = regulator_enable(twl->usb3v1);
+ if (res)
+ dev_err(twl->dev, "Failed to enable usb3v1\n");
+
+ res = regulator_enable(twl->usb1v8);
+ if (res)
+ dev_err(twl->dev, "Failed to enable usb1v8\n");
+
+ /*
+ * Disabling usb3v1 regulator (= writing 0 to VUSB3V1_DEV_GRP
+ * in twl4030) resets the VUSB_DEDICATED2 register. This reset
+ * enables VUSB3V1_SLEEP bit that remaps usb3v1 ACTIVE state to
+ * SLEEP. We work around this by clearing the bit after usv3v1
+ * is re-activated. This ensures that VUSB3V1 is really active.
+ */
+ twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2);
+
+ res = regulator_enable(twl->usb1v5);
+ if (res)
+ dev_err(twl->dev, "Failed to enable usb1v5\n");
+
+ __twl4030_phy_power(twl, 1);
+ twl4030_usb_write(twl, PHY_CLK_CTRL,
+ twl4030_usb_read(twl, PHY_CLK_CTRL) |
+ (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;
+}
+
+static int twl4030_phy_power_off(struct phy *phy)
+{
+ struct twl4030_usb *twl = phy_get_drvdata(phy);
+
+ dev_dbg(twl->dev, "%s\n", __func__);
+
+ return 0;
+}
+
+static int twl4030_phy_power_on(struct phy *phy)
+{
+ struct twl4030_usb *twl = phy_get_drvdata(phy);
+
+ dev_dbg(twl->dev, "%s\n", __func__);
+ pm_runtime_get_sync(twl->dev);
+ schedule_delayed_work(&twl->id_workaround_work, HZ);
+ pm_runtime_mark_last_busy(twl->dev);
+ pm_runtime_put_autosuspend(twl->dev);
+
+ return 0;
+}
+
+static int twl4030_usb_ldo_init(struct twl4030_usb *twl)
+{
+ /* Enable writing to power configuration registers */
+ twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG1,
+ TWL4030_PM_MASTER_PROTECT_KEY);
+
+ twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG2,
+ TWL4030_PM_MASTER_PROTECT_KEY);
+
+ /* Keep VUSB3V1 LDO in sleep state until VBUS/ID change detected*/
+ /*twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2);*/
+
+ /* input to VUSB3V1 LDO is from VBAT, not VBUS */
+ twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0x14, VUSB_DEDICATED1);
+
+ /* Initialize 3.1V regulator */
+ twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB3V1_DEV_GRP);
+
+ twl->usb3v1 = devm_regulator_get(twl->dev, "usb3v1");
+ if (IS_ERR(twl->usb3v1))
+ return -ENODEV;
+
+ twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB3V1_TYPE);
+
+ /* Initialize 1.5V regulator */
+ twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V5_DEV_GRP);
+
+ twl->usb1v5 = devm_regulator_get(twl->dev, "usb1v5");
+ if (IS_ERR(twl->usb1v5))
+ return -ENODEV;
+
+ twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V5_TYPE);
+
+ /* Initialize 1.8V regulator */
+ twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V8_DEV_GRP);
+
+ twl->usb1v8 = devm_regulator_get(twl->dev, "usb1v8");
+ if (IS_ERR(twl->usb1v8))
+ return -ENODEV;
+
+ twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V8_TYPE);
+
+ /* disable access to power configuration registers */
+ twl_i2c_write_u8(TWL_MODULE_PM_MASTER, 0,
+ TWL4030_PM_MASTER_PROTECT_KEY);
+
+ return 0;
+}
+
+static ssize_t twl4030_usb_vbus_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct twl4030_usb *twl = dev_get_drvdata(dev);
+ int ret = -EINVAL;
+
+ mutex_lock(&twl->lock);
+ ret = sprintf(buf, "%s\n",
+ twl->vbus_supplied ? "on" : "off");
+ mutex_unlock(&twl->lock);
+
+ return ret;
+}
+static DEVICE_ATTR(vbus, 0444, twl4030_usb_vbus_show, NULL);
+
+static irqreturn_t twl4030_usb_irq(int irq, void *_twl)
+{
+ struct twl4030_usb *twl = _twl;
+ enum musb_vbus_id_status status;
+ bool status_changed = false;
+ int err;
+
+ status = twl4030_usb_linkstat(twl);
+
+ mutex_lock(&twl->lock);
+ if (status >= 0 && status != twl->linkstat) {
+ status_changed =
+ cable_present(twl->linkstat) !=
+ cable_present(status);
+ twl->linkstat = status;
+ }
+ mutex_unlock(&twl->lock);
+
+ if (status_changed) {
+ /* FIXME add a set_power() method so that B-devices can
+ * configure the charger appropriately. It's not always
+ * correct to consume VBUS power, and how much current to
+ * consume is a function of the USB configuration chosen
+ * by the host.
+ *
+ * REVISIT usb_gadget_vbus_connect(...) as needed, ditto
+ * its disconnect() sibling, when changing to/from the
+ * USB_LINK_VBUS state. musb_hdrc won't care until it
+ * starts to handle softconnect right.
+ */
+ if (cable_present(status)) {
+ pm_runtime_get_sync(twl->dev);
+ } else {
+ 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->musb_mailbox_pending = false;
+ }
+
+ /* don't schedule during sleep - irq works right then */
+ if (status == MUSB_ID_GROUND && pm_runtime_active(twl->dev)) {
+ cancel_delayed_work(&twl->id_workaround_work);
+ schedule_delayed_work(&twl->id_workaround_work, HZ);
+ }
+
+ if (irq)
+ sysfs_notify(&twl->dev->kobj, NULL, "vbus");
+
+ return IRQ_HANDLED;
+}
+
+static void twl4030_id_workaround_work(struct work_struct *work)
+{
+ struct twl4030_usb *twl = container_of(work, struct twl4030_usb,
+ id_workaround_work.work);
+
+ twl4030_usb_irq(0, twl);
+}
+
+static int twl4030_phy_init(struct phy *phy)
+{
+ struct twl4030_usb *twl = phy_get_drvdata(phy);
+
+ pm_runtime_get_sync(twl->dev);
+ twl->linkstat = MUSB_UNKNOWN;
+ schedule_delayed_work(&twl->id_workaround_work, HZ);
+ pm_runtime_mark_last_busy(twl->dev);
+ pm_runtime_put_autosuspend(twl->dev);
+
+ return 0;
+}
+
+static int twl4030_set_peripheral(struct usb_otg *otg,
+ struct usb_gadget *gadget)
+{
+ if (!otg)
+ return -ENODEV;
+
+ otg->gadget = gadget;
+ if (!gadget)
+ otg->state = OTG_STATE_UNDEFINED;
+
+ return 0;
+}
+
+static int twl4030_set_host(struct usb_otg *otg, struct usb_bus *host)
+{
+ if (!otg)
+ return -ENODEV;
+
+ otg->host = host;
+ if (!host)
+ otg->state = OTG_STATE_UNDEFINED;
+
+ return 0;
+}
+
+static const struct phy_ops ops = {
+ .init = twl4030_phy_init,
+ .power_on = twl4030_phy_power_on,
+ .power_off = twl4030_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static const struct dev_pm_ops twl4030_usb_pm_ops = {
+ SET_RUNTIME_PM_OPS(twl4030_usb_runtime_suspend,
+ twl4030_usb_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(twl4030_usb_suspend, twl4030_usb_resume)
+};
+
+static int twl4030_usb_probe(struct platform_device *pdev)
+{
+ struct twl4030_usb_data *pdata = dev_get_platdata(&pdev->dev);
+ struct twl4030_usb *twl;
+ struct phy *phy;
+ int status, err;
+ struct usb_otg *otg;
+ struct device_node *np = pdev->dev.of_node;
+ struct phy_provider *phy_provider;
+
+ twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL);
+ if (!twl)
+ return -ENOMEM;
+
+ if (np)
+ of_property_read_u32(np, "usb_mode",
+ (enum twl4030_usb_mode *)&twl->usb_mode);
+ else if (pdata) {
+ twl->usb_mode = pdata->usb_mode;
+ } else {
+ dev_err(&pdev->dev, "twl4030 initialized without pdata\n");
+ return -EINVAL;
+ }
+
+ otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL);
+ if (!otg)
+ return -ENOMEM;
+
+ twl->dev = &pdev->dev;
+ 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";
+ twl->phy.otg = otg;
+ twl->phy.type = USB_PHY_TYPE_USB2;
+
+ otg->usb_phy = &twl->phy;
+ otg->set_host = twl4030_set_host;
+ otg->set_peripheral = twl4030_set_peripheral;
+
+ phy = devm_phy_create(twl->dev, NULL, &ops);
+ if (IS_ERR(phy)) {
+ dev_dbg(&pdev->dev, "Failed to create PHY\n");
+ return PTR_ERR(phy);
+ }
+
+ phy_set_drvdata(phy, twl);
+
+ phy_provider = devm_of_phy_provider_register(twl->dev,
+ of_phy_simple_xlate);
+ if (IS_ERR(phy_provider))
+ return PTR_ERR(phy_provider);
+
+ /* init mutex for workqueue */
+ mutex_init(&twl->lock);
+
+ INIT_DELAYED_WORK(&twl->id_workaround_work, twl4030_id_workaround_work);
+
+ err = twl4030_usb_ldo_init(twl);
+ if (err) {
+ dev_err(&pdev->dev, "ldo init failed\n");
+ return err;
+ }
+ usb_add_phy_dev(&twl->phy);
+
+ platform_set_drvdata(pdev, twl);
+ if (device_create_file(&pdev->dev, &dev_attr_vbus))
+ dev_warn(&pdev->dev, "could not create sysfs file\n");
+
+ ATOMIC_INIT_NOTIFIER_HEAD(&twl->phy.notifier);
+
+ pm_runtime_use_autosuspend(&pdev->dev);
+ pm_runtime_set_autosuspend_delay(&pdev->dev, 2000);
+ pm_runtime_enable(&pdev->dev);
+ pm_runtime_get_sync(&pdev->dev);
+
+ /* Our job is to use irqs and status from the power module
+ * to keep the transceiver disabled when nothing's connected.
+ *
+ * FIXME we actually shouldn't start enabling it until the
+ * USB controller drivers have said they're ready, by calling
+ * set_host() and/or set_peripheral() ... OTG_capable boards
+ * need both handles, otherwise just one suffices.
+ */
+ status = devm_request_threaded_irq(twl->dev, twl->irq, NULL,
+ twl4030_usb_irq, IRQF_TRIGGER_FALLING |
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT, "twl4030_usb", twl);
+ if (status < 0) {
+ dev_dbg(&pdev->dev, "can't get IRQ %d, err %d\n",
+ twl->irq, status);
+ return status;
+ }
+
+ if (pdata)
+ err = phy_create_lookup(phy, "usb", "musb-hdrc.0");
+ if (err)
+ return err;
+
+ pm_runtime_mark_last_busy(&pdev->dev);
+ pm_runtime_put_autosuspend(twl->dev);
+
+ dev_info(&pdev->dev, "Initialized TWL4030 USB module\n");
+ return 0;
+}
+
+static int twl4030_usb_remove(struct platform_device *pdev)
+{
+ struct twl4030_usb *twl = platform_get_drvdata(pdev);
+ int val;
+
+ usb_remove_phy(&twl->phy);
+ pm_runtime_get_sync(twl->dev);
+ cancel_delayed_work_sync(&twl->id_workaround_work);
+ device_remove_file(twl->dev, &dev_attr_vbus);
+
+ /* set transceiver mode to power on defaults */
+ twl4030_usb_set_mode(twl, -1);
+
+ /* idle ulpi before powering off */
+ if (cable_present(twl->linkstat))
+ pm_runtime_put_noidle(twl->dev);
+ pm_runtime_mark_last_busy(twl->dev);
+ pm_runtime_dont_use_autosuspend(&pdev->dev);
+ pm_runtime_put_sync(twl->dev);
+ pm_runtime_disable(twl->dev);
+
+ /* autogate 60MHz ULPI clock,
+ * clear dpll clock request for i2c access,
+ * disable 32KHz
+ */
+ val = twl4030_usb_read(twl, PHY_CLK_CTRL);
+ if (val >= 0) {
+ val |= PHY_CLK_CTRL_CLOCKGATING_EN;
+ val &= ~(PHY_CLK_CTRL_CLK32K_EN | REQ_PHY_DPLL_CLK);
+ twl4030_usb_write(twl, PHY_CLK_CTRL, (u8)val);
+ }
+
+ /* disable complete OTG block */
+ twl4030_usb_clear_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id twl4030_usb_id_table[] = {
+ { .compatible = "ti,twl4030-usb" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, twl4030_usb_id_table);
+#endif
+
+static struct platform_driver twl4030_usb_driver = {
+ .probe = twl4030_usb_probe,
+ .remove = twl4030_usb_remove,
+ .driver = {
+ .name = "twl4030_usb",
+ .pm = &twl4030_usb_pm_ops,
+ .of_match_table = of_match_ptr(twl4030_usb_id_table),
+ },
+};
+
+static int __init twl4030_usb_init(void)
+{
+ return platform_driver_register(&twl4030_usb_driver);
+}
+subsys_initcall(twl4030_usb_init);
+
+static void __exit twl4030_usb_exit(void)
+{
+ platform_driver_unregister(&twl4030_usb_driver);
+}
+module_exit(twl4030_usb_exit);
+
+MODULE_ALIAS("platform:twl4030_usb");
+MODULE_AUTHOR("Texas Instruments, Inc, Nokia Corporation");
+MODULE_DESCRIPTION("TWL4030 USB transceiver driver");
+MODULE_LICENSE("GPL");