summaryrefslogtreecommitdiffstats
path: root/drivers/phy
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2018-03-20 10:10:46 +0100
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2018-03-20 10:10:46 +0100
commit85a09bf492333300ca0a09199bce4cf1a5bdd5cb (patch)
tree8209918ef0d536cb401247f315a3aba60b15bdf5 /drivers/phy
parenta8f25c36f7322fd089e6d006b4e3708038882561 (diff)
parente7f4da4c44fe74e1c9277bac9b12ea1ef4eb70db (diff)
downloadlinux-85a09bf492333300ca0a09199bce4cf1a5bdd5cb.tar.bz2
Merge tag 'phy-for-4.17' of git://git.kernel.org/pub/scm/linux/kernel/git/kishon/linux-phy into usb-next
Kishon writes: phy: for 4.17 *) Add USB PHY driver for MDM6600 on Droid *) Add USB PHY driver for STM32 USB PHY Controller *) Add inno-usb2-phy driver for hi3798cv200 SoC *) Add combo phy driver (SATA/USB/PCIE) for HiSilicon STB SoCs *) Add USB3 PHY driver for Meson GXL and GXM *) Add support for R8A77965 Gen3 USB 2.0 PHY in phy-rcar-gen3-usb2 driver *) Add support for qualcomm QUSB2 V2 and QMP V3 USB3 PHY in phy-qcom-qusb2 and phy-qcom-qmp PHY driver respectively *) Add support for runtime PM in phy-qcom-qusb2 and phy-qcom-qmp PHY drivers *) Add support for Allwinner R40 USB PHY in sun4i-usb PHY driver *) Add support in rockchip-typec PHY driver to make extcon optional and fallback to working in host mode if extcon is missing *) Add support in rockchip-typec PHY driver to mux PHYs connected to DP *) Add support to configure slew rate parameters in phy-mtk-tphy PHY driver *) Add workaround for missing Vbus det interrupts on Allwinner A23/A33 *) Add USB speed related PHY modes in phy core *) Fix PHY 'structure' documentation *) Force rockchip-typec PHY to USB2 if DP-only mode is used *) Fix phy-qcom-qusb2 and phy-qcom-qmp PHY drivers to follow PHY reset and initialization sequence as per hardware programming manual *) Fix Marvell BG2CD SoC USB failure in phy-berlin-usb driver *) Minor fixes in lpc18xx-usb-otg, xusb-tegra210 and phy-rockchip-emmc PHY drivers Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
Diffstat (limited to 'drivers/phy')
-rw-r--r--drivers/phy/allwinner/phy-sun4i-usb.c22
-rw-r--r--drivers/phy/amlogic/Kconfig13
-rw-r--r--drivers/phy/amlogic/Makefile1
-rw-r--r--drivers/phy/amlogic/phy-meson-gxl-usb2.c78
-rw-r--r--drivers/phy/amlogic/phy-meson-gxl-usb3.c282
-rw-r--r--drivers/phy/hisilicon/Kconfig20
-rw-r--r--drivers/phy/hisilicon/Makefile2
-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/marvell/phy-berlin-usb.c2
-rw-r--r--drivers/phy/mediatek/phy-mtk-tphy.c23
-rw-r--r--drivers/phy/motorola/Kconfig8
-rw-r--r--drivers/phy/motorola/Makefile1
-rw-r--r--drivers/phy/motorola/phy-mapphone-mdm6600.c542
-rw-r--r--drivers/phy/phy-core.c2
-rw-r--r--drivers/phy/phy-lpc18xx-usb-otg.c8
-rw-r--r--drivers/phy/qualcomm/phy-qcom-qmp.c647
-rw-r--r--drivers/phy/qualcomm/phy-qcom-qmp.h280
-rw-r--r--drivers/phy/qualcomm/phy-qcom-qusb2.c418
-rw-r--r--drivers/phy/ralink/Kconfig1
-rw-r--r--drivers/phy/renesas/phy-rcar-gen3-usb2.c4
-rw-r--r--drivers/phy/rockchip/Kconfig1
-rw-r--r--drivers/phy/rockchip/phy-rockchip-emmc.c60
-rw-r--r--drivers/phy/rockchip/phy-rockchip-typec.c160
-rw-r--r--drivers/phy/samsung/Kconfig2
-rw-r--r--drivers/phy/st/Kconfig14
-rw-r--r--drivers/phy/st/Makefile1
-rw-r--r--drivers/phy/st/phy-stm32-usbphyc.c461
-rw-r--r--drivers/phy/tegra/xusb-tegra210.c6
-rw-r--r--drivers/phy/tegra/xusb.c2
30 files changed, 3153 insertions, 394 deletions
diff --git a/drivers/phy/allwinner/phy-sun4i-usb.c b/drivers/phy/allwinner/phy-sun4i-usb.c
index aa857be692cf..d4dcd39b8d76 100644
--- a/drivers/phy/allwinner/phy-sun4i-usb.c
+++ b/drivers/phy/allwinner/phy-sun4i-usb.c
@@ -112,6 +112,7 @@ enum sun4i_usb_phy_type {
sun8i_a33_phy,
sun8i_a83t_phy,
sun8i_h3_phy,
+ sun8i_r40_phy,
sun8i_v3s_phy,
sun50i_a64_phy,
};
@@ -410,11 +411,13 @@ static bool sun4i_usb_phy0_poll(struct sun4i_usb_phy_data *data)
return true;
/*
- * The A31 companion pmic (axp221) does not generate vbus change
- * interrupts when the board is driving vbus, so we must poll
+ * 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 &&
+ if ((data->cfg->type == sun6i_a31_phy ||
+ data->cfg->type == sun8i_a33_phy) &&
data->vbus_power_supply && data->phys[0].regulator_on)
return true;
@@ -885,7 +888,7 @@ static const struct sun4i_usb_phy_cfg sun7i_a20_cfg = {
static const struct sun4i_usb_phy_cfg sun8i_a23_cfg = {
.num_phys = 2,
- .type = sun4i_a10_phy,
+ .type = sun6i_a31_phy,
.disc_thresh = 3,
.phyctl_offset = REG_PHYCTL_A10,
.dedicated_clocks = true,
@@ -919,6 +922,16 @@ static const struct sun4i_usb_phy_cfg sun8i_h3_cfg = {
.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,
@@ -948,6 +961,7 @@ static const struct of_device_id sun4i_usb_phy_of_match[] = {
{ .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},
diff --git a/drivers/phy/amlogic/Kconfig b/drivers/phy/amlogic/Kconfig
index cb8f4501652b..23fe1cda2f70 100644
--- a/drivers/phy/amlogic/Kconfig
+++ b/drivers/phy/amlogic/Kconfig
@@ -18,10 +18,21 @@ config PHY_MESON_GXL_USB2
default ARCH_MESON
depends on OF && (ARCH_MESON || COMPILE_TEST)
depends on USB_SUPPORT
- select USB_COMMON
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
index cfdc98715c30..4fd8848c194d 100644
--- a/drivers/phy/amlogic/Makefile
+++ b/drivers/phy/amlogic/Makefile
@@ -1,2 +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
index e90c4ee25dfe..9f9b5414b97a 100644
--- a/drivers/phy/amlogic/phy-meson-gxl-usb2.c
+++ b/drivers/phy/amlogic/phy-meson-gxl-usb2.c
@@ -11,14 +11,15 @@
* 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>
-#include <linux/usb/of.h>
/* bits [31:27] are read-only */
#define U2P_R0 0x0
@@ -70,12 +71,11 @@
/* bits [31:14] are read-only */
#define U2P_R2 0x8
- #define U2P_R2_DATA_IN_MASK GENMASK(3, 0)
- #define U2P_R2_DATA_IN_EN_MASK GENMASK(7, 4)
- #define U2P_R2_ADDR_MASK GENMASK(11, 8)
- #define U2P_R2_DATA_OUT_SEL BIT(12)
- #define U2P_R2_CLK BIT(13)
- #define U2P_R2_DATA_OUT_MASK GENMASK(17, 14)
+ #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)
@@ -99,6 +99,8 @@ 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 = {
@@ -108,6 +110,31 @@ static const struct regmap_config phy_meson_gxl_usb2_regmap_conf = {
.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);
@@ -195,6 +222,8 @@ static int phy_meson_gxl_usb2_power_on(struct phy *phy)
}
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,
@@ -210,6 +239,7 @@ static int phy_meson_gxl_usb2_probe(struct platform_device *pdev)
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)
@@ -222,28 +252,34 @@ static int phy_meson_gxl_usb2_probe(struct platform_device *pdev)
if (IS_ERR(base))
return PTR_ERR(base);
- switch (of_usb_get_dr_mode_by_phy(dev->of_node, -1)) {
- case USB_DR_MODE_PERIPHERAL:
- priv->mode = PHY_MODE_USB_DEVICE;
- break;
- case USB_DR_MODE_OTG:
- priv->mode = PHY_MODE_USB_OTG;
- break;
- case USB_DR_MODE_HOST:
- default:
- priv->mode = PHY_MODE_USB_HOST;
- break;
- }
+ /* 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)) {
- dev_err(dev, "failed to create PHY\n");
- return PTR_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);
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 000000000000..d37d94ddf9c0
--- /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/hisilicon/Kconfig b/drivers/phy/hisilicon/Kconfig
index 6164c4cd0f65..b40ee54a1a50 100644
--- a/drivers/phy/hisilicon/Kconfig
+++ b/drivers/phy/hisilicon/Kconfig
@@ -4,6 +4,7 @@
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
@@ -11,6 +12,25 @@ config PHY_HI6220_USB
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
diff --git a/drivers/phy/hisilicon/Makefile b/drivers/phy/hisilicon/Makefile
index 541b348187a8..f662a4fe18d8 100644
--- a/drivers/phy/hisilicon/Makefile
+++ b/drivers/phy/hisilicon/Makefile
@@ -1,2 +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-hisi-inno-usb2.c b/drivers/phy/hisilicon/phy-hisi-inno-usb2.c
new file mode 100644
index 000000000000..524381249a2b
--- /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 000000000000..5777b3120017
--- /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/marvell/phy-berlin-usb.c b/drivers/phy/marvell/phy-berlin-usb.c
index 2017751ede26..8f2b5cae360f 100644
--- a/drivers/phy/marvell/phy-berlin-usb.c
+++ b/drivers/phy/marvell/phy-berlin-usb.c
@@ -127,7 +127,7 @@ static int phy_berlin_usb_power_on(struct phy *phy)
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_260 | SQ_THRESHOLD(0xa) | LPF_COEF(0x2) |
+ 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);
diff --git a/drivers/phy/mediatek/phy-mtk-tphy.c b/drivers/phy/mediatek/phy-mtk-tphy.c
index 1e96d0740ef5..38c281b5abbb 100644
--- a/drivers/phy/mediatek/phy-mtk-tphy.c
+++ b/drivers/phy/mediatek/phy-mtk-tphy.c
@@ -306,6 +306,8 @@ struct mtk_tphy {
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,
@@ -360,16 +362,17 @@ static void hs_slew_rate_calibrate(struct mtk_tphy *tphy,
writel(tmp, fmreg + U3P_U2FREQ_FMMONR1);
if (fm_out) {
- /* ( 1024 / FM_OUT ) x reference clock frequency x 0.028 */
- tmp = U3P_FM_DET_CYCLE_CNT * U3P_REF_CLK * U3P_SLEW_RATE_COEF;
- tmp /= 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\n",
- instance->index, fm_out, calibration_val);
+ 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);
@@ -688,8 +691,7 @@ static void pcie_phy_instance_power_on(struct mtk_tphy *tphy,
u32 tmp;
tmp = readl(bank->chip + U3P_U3_CHIP_GPIO_CTLD);
- tmp &= ~(P3C_FORCE_IP_SW_RST | P3C_MCU_BUS_CK_GATE_EN |
- P3C_REG_IP_SW_RST);
+ 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);
@@ -1042,6 +1044,13 @@ static int mtk_tphy_probe(struct platform_device *pdev)
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;
diff --git a/drivers/phy/motorola/Kconfig b/drivers/phy/motorola/Kconfig
index 6bb7d6bdf1bf..82651524ffb9 100644
--- a/drivers/phy/motorola/Kconfig
+++ b/drivers/phy/motorola/Kconfig
@@ -10,3 +10,11 @@ config PHY_CPCAP_USB
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
+ 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
index b6cd618d671a..3514f985b355 100644
--- a/drivers/phy/motorola/Makefile
+++ b/drivers/phy/motorola/Makefile
@@ -3,3 +3,4 @@
#
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-mapphone-mdm6600.c b/drivers/phy/motorola/phy-mapphone-mdm6600.c
new file mode 100644
index 000000000000..5439dd90d0dd
--- /dev/null
+++ b/drivers/phy/motorola/phy-mapphone-mdm6600.c
@@ -0,0 +1,542 @@
+// 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 */
+
+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 completion ack;
+ bool enabled; /* mdm6600 phy enabled */
+ bool running; /* mdm6600 boot done */
+ 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_CMD_LINES,
+ ddata->status_gpios->desc,
+ values);
+ if (error)
+ return;
+
+ for (i = 0; i < PHY_MDM6600_NR_CMD_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;
+
+ mode_gpio1 = ddata->mode_gpios->desc[PHY_MDM6600_MODE1];
+ dev_dbg(ddata->dev, "OOB wake on mode_gpio1: %i\n",
+ gpiod_get_value(mode_gpio1));
+
+ 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");
+}
+
+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_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);
+
+ 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];
+
+ 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->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",
+ .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
index 8f6e8e28996d..09ac8afb97ac 100644
--- a/drivers/phy/phy-core.c
+++ b/drivers/phy/phy-core.c
@@ -351,6 +351,8 @@ int phy_set_mode(struct phy *phy, enum phy_mode mode)
mutex_lock(&phy->mutex);
ret = phy->ops->set_mode(phy, mode);
+ if (!ret)
+ phy->attrs.mode = mode;
mutex_unlock(&phy->mutex);
return ret;
diff --git a/drivers/phy/phy-lpc18xx-usb-otg.c b/drivers/phy/phy-lpc18xx-usb-otg.c
index 3b7a71eb5b7e..7de280a45421 100644
--- a/drivers/phy/phy-lpc18xx-usb-otg.c
+++ b/drivers/phy/phy-lpc18xx-usb-otg.c
@@ -60,8 +60,14 @@ static int lpc18xx_usb_otg_phy_power_on(struct phy *phy)
return ret;
/* The bit in CREG is cleared to enable the PHY */
- return regmap_update_bits(lpc->reg, LPC18XX_CREG_CREG0,
+ 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)
diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c
index e17f0351ccc2..6470c5d61d1c 100644
--- a/drivers/phy/qualcomm/phy-qcom-qmp.c
+++ b/drivers/phy/qualcomm/phy-qcom-qmp.c
@@ -1,15 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2017, 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/clk.h>
@@ -31,124 +22,7 @@
#include <dt-bindings/phy/phy.h>
-/* QMP 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
-
-/* QMP PHY TX registers */
-#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
-
-/* QMP 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
-
-/* QMP 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
+#include "phy-qcom-qmp.h"
/* QPHY_SW_RESET bit */
#define SW_RESET BIT(0)
@@ -164,6 +38,34 @@
/* 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 1000
#define POWER_DOWN_DELAY_US_MIN 10
#define POWER_DOWN_DELAY_US_MAX 11
@@ -210,6 +112,9 @@ enum qphy_reg_layout {
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[] = {
@@ -237,6 +142,18 @@ static const unsigned int usb3phy_regs_layout[] = {
[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[] = {
@@ -467,6 +384,112 @@ static const struct qmp_phy_init_tbl ipq8074_pcie_pcs_tbl[] = {
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),
+};
+
/* struct qmp_phy_cfg - per-PHY initialization config */
struct qmp_phy_cfg {
/* phy-type - PCIE/UFS/USB */
@@ -511,6 +534,12 @@ struct qmp_phy_cfg {
/* 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;
};
/**
@@ -520,6 +549,7 @@ struct qmp_phy_cfg {
* @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
@@ -530,6 +560,7 @@ struct qmp_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;
@@ -541,6 +572,7 @@ struct qmp_phy {
*
* @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
@@ -550,12 +582,15 @@ struct qmp_phy {
* @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 **clks;
+ struct clk_bulk_data *clks;
struct reset_control **resets;
struct regulator_bulk_data *vregs;
@@ -564,6 +599,8 @@ struct qcom_qmp {
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)
@@ -595,6 +632,10 @@ 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",
@@ -701,6 +742,38 @@ static const struct qmp_phy_cfg ipq8074_pciephy_cfg = {
.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,
+
+ .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 void qcom_qmp_phy_configure(void __iomem *base,
const unsigned int *regs,
const struct qmp_phy_init_tbl tbl[],
@@ -724,44 +797,20 @@ static int qcom_qmp_phy_poweron(struct phy *phy)
{
struct qmp_phy *qphy = phy_get_drvdata(phy);
struct qcom_qmp *qmp = qphy->qmp;
- int num = qmp->cfg->num_vregs;
int ret;
- dev_vdbg(&phy->dev, "Powering on QMP phy\n");
-
- /* turn on regulator supplies */
- ret = regulator_bulk_enable(num, qmp->vregs);
- if (ret) {
- dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret);
- return ret;
- }
-
ret = clk_prepare_enable(qphy->pipe_clk);
- if (ret) {
+ if (ret)
dev_err(qmp->dev, "pipe_clk enable failed, err=%d\n", ret);
- regulator_bulk_disable(num, qmp->vregs);
- return ret;
- }
- return 0;
-}
-
-static int qcom_qmp_phy_poweroff(struct phy *phy)
-{
- struct qmp_phy *qphy = phy_get_drvdata(phy);
- struct qcom_qmp *qmp = qphy->qmp;
-
- clk_disable_unprepare(qphy->pipe_clk);
-
- regulator_bulk_disable(qmp->cfg->num_vregs, qmp->vregs);
-
- return 0;
+ return ret;
}
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);
@@ -770,7 +819,23 @@ static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp)
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",
@@ -779,10 +844,33 @@ static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp)
}
}
+ 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);
@@ -803,7 +891,7 @@ static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp)
if (ret) {
dev_err(qmp->dev,
"phy common block init timed-out\n");
- goto err_rst;
+ goto err_com_init;
}
}
@@ -811,9 +899,14 @@ static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp)
return 0;
+err_com_init:
+ clk_bulk_disable_unprepare(cfg->num_clks, qmp->clks);
err_rst:
- while (--i >= 0)
+ 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;
@@ -843,6 +936,10 @@ static int qcom_qmp_phy_com_exit(struct qcom_qmp *qmp)
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;
@@ -857,24 +954,16 @@ static int qcom_qmp_phy_init(struct phy *phy)
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, i;
+ int ret;
dev_vdbg(qmp->dev, "Initializing QMP phy\n");
- for (i = 0; i < qmp->cfg->num_clks; i++) {
- ret = clk_prepare_enable(qmp->clks[i]);
- if (ret) {
- dev_err(qmp->dev, "failed to enable %s clk, err=%d\n",
- qmp->cfg->clk_list[i], ret);
- goto err_clk;
- }
- }
-
ret = qcom_qmp_phy_com_init(qmp);
if (ret)
- goto err_clk;
+ return ret;
if (cfg->has_lane_rst) {
ret = reset_control_deassert(qphy->lane_rst);
@@ -887,7 +976,16 @@ static int qcom_qmp_phy_init(struct phy *phy)
/* 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);
/*
@@ -899,11 +997,13 @@ static int qcom_qmp_phy_init(struct phy *phy)
if (cfg->has_pwrdn_delay)
usleep_range(cfg->pwrdn_delay_min, cfg->pwrdn_delay_max);
- /* start SerDes and Phy-Coding-Sublayer */
- qphy_setbits(pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl);
-
/* 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;
@@ -914,6 +1014,7 @@ static int qcom_qmp_phy_init(struct phy *phy)
dev_err(qmp->dev, "phy initialization timed-out\n");
goto err_pcs_ready;
}
+ qmp->phy_initialized = true;
return ret;
@@ -922,9 +1023,6 @@ err_pcs_ready:
reset_control_assert(qphy->lane_rst);
err_lane_rst:
qcom_qmp_phy_com_exit(qmp);
-err_clk:
- while (--i >= 0)
- clk_disable_unprepare(qmp->clks[i]);
return ret;
}
@@ -934,7 +1032,8 @@ 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;
- int i = cfg->num_clks;
+
+ clk_disable_unprepare(qphy->pipe_clk);
/* PHY reset */
qphy_setbits(qphy->pcs, cfg->regs[QPHY_SW_RESET], SW_RESET);
@@ -950,8 +1049,127 @@ static int qcom_qmp_phy_exit(struct phy *phy)
qcom_qmp_phy_com_exit(qmp);
- while (--i >= 0)
- clk_disable_unprepare(qmp->clks[i]);
+ 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;
}
@@ -1000,29 +1218,17 @@ static int qcom_qmp_phy_reset_init(struct device *dev)
static int qcom_qmp_phy_clk_init(struct device *dev)
{
struct qcom_qmp *qmp = dev_get_drvdata(dev);
- int ret, i;
+ int num = qmp->cfg->num_clks;
+ int i;
- qmp->clks = devm_kcalloc(dev, qmp->cfg->num_clks,
- sizeof(*qmp->clks), GFP_KERNEL);
+ qmp->clks = devm_kcalloc(dev, num, sizeof(*qmp->clks), GFP_KERNEL);
if (!qmp->clks)
return -ENOMEM;
- for (i = 0; i < qmp->cfg->num_clks; i++) {
- struct clk *_clk;
- const char *name = qmp->cfg->clk_list[i];
-
- _clk = devm_clk_get(dev, name);
- if (IS_ERR(_clk)) {
- ret = PTR_ERR(_clk);
- if (ret != -EPROBE_DEFER)
- dev_err(dev, "failed to get %s clk, %d\n",
- name, ret);
- return ret;
- }
- qmp->clks[i] = _clk;
- }
+ for (i = 0; i < num; i++)
+ qmp->clks[i].id = qmp->cfg->clk_list[i];
- return 0;
+ return devm_clk_bulk_get(dev, num, qmp->clks);
}
/*
@@ -1078,7 +1284,7 @@ static const struct phy_ops qcom_qmp_phy_gen_ops = {
.init = qcom_qmp_phy_init,
.exit = qcom_qmp_phy_exit,
.power_on = qcom_qmp_phy_poweron,
- .power_off = qcom_qmp_phy_poweroff,
+ .set_mode = qcom_qmp_phy_set_mode,
.owner = THIS_MODULE,
};
@@ -1097,7 +1303,8 @@ int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id)
/*
* Get memory resources for each phy lane:
- * Resources are indexed as: tx -> 0; rx -> 1; pcs -> 2.
+ * Resources are indexed as: tx -> 0; rx -> 1; pcs -> 2; and
+ * pcs_misc (optional) -> 3.
*/
qphy->tx = of_iomap(np, 0);
if (!qphy->tx)
@@ -1111,6 +1318,10 @@ int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id)
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,
@@ -1169,11 +1380,19 @@ static const struct of_device_id qcom_qmp_phy_of_match_table[] = {
}, {
.compatible = "qcom,ipq8074-qmp-pcie-phy",
.data = &ipq8074_pciephy_cfg,
+ }, {
+ .compatible = "qcom,qmp-v3-usb3-phy",
+ .data = &qmp_v3_usb3phy_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;
@@ -1192,6 +1411,11 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev)
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))
@@ -1200,10 +1424,18 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev)
/* per PHY serdes; usually located at base address */
qmp->serdes = base;
- mutex_init(&qmp->phy_mutex);
+ /* 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);
- /* Get the specific init parameters of QMP phy */
- qmp->cfg = of_device_get_match_data(dev);
+ qmp->dp_com = base;
+ }
+
+ mutex_init(&qmp->phy_mutex);
ret = qcom_qmp_phy_clk_init(dev);
if (ret)
@@ -1229,12 +1461,21 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev)
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;
}
@@ -1246,6 +1487,7 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev)
if (ret) {
dev_err(qmp->dev,
"failed to register pipe clock source\n");
+ pm_runtime_disable(dev);
return ret;
}
id++;
@@ -1254,6 +1496,8 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev)
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);
}
@@ -1262,6 +1506,7 @@ 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,
},
};
diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.h b/drivers/phy/qualcomm/phy-qcom-qmp.h
new file mode 100644
index 000000000000..d1c6905d0439
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-qmp.h
@@ -0,0 +1,280 @@
+// 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_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_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
+
+/* 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
+
+/* 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
index 6c575244c0fb..94afeac1a19e 100644
--- a/drivers/phy/qualcomm/phy-qcom-qusb2.c
+++ b/drivers/phy/qualcomm/phy-qcom-qusb2.c
@@ -1,14 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2017, 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/clk.h>
@@ -37,28 +29,57 @@
#define QUSB2PHY_PLL_AUTOPGM_CTL1 0x1c
#define QUSB2PHY_PLL_PWR_CTRL 0x18
-#define QUSB2PHY_PLL_STATUS 0x38
+/* QUSB2PHY_PLL_STATUS register bits */
#define PLL_LOCKED BIT(5)
-#define QUSB2PHY_PORT_TUNE1 0x80
-#define QUSB2PHY_PORT_TUNE2 0x84
-#define QUSB2PHY_PORT_TUNE3 0x88
-#define QUSB2PHY_PORT_TUNE4 0x8c
-#define QUSB2PHY_PORT_TUNE5 0x90
-#define QUSB2PHY_PORT_TEST2 0x9c
+/* QUSB2PHY_PLL_COMMON_STATUS_ONE register bits */
+#define CORE_READY_STATUS BIT(0)
-#define QUSB2PHY_PORT_POWERDOWN 0xb4
+/* 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)
+
+#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) \
@@ -67,30 +88,136 @@ struct qusb2_phy_init_tbl {
.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(QUSB2PHY_PORT_TUNE1, 0xf8),
- QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE2, 0xb3),
- QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE3, 0x83),
- QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE4, 0xc0),
+ 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(QUSB2PHY_PORT_TEST2, 0x14),
+
+ 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 qusb2_v2_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 qusb2_v2_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),
+ .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 qusb2_v2_phy_cfg = {
+ .tbl = qusb2_v2_init_tbl,
+ .tbl_num = ARRAY_SIZE(qusb2_v2_init_tbl),
+ .regs = qusb2_v2_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),
};
static const char * const qusb2_phy_vreg_names[] = {
@@ -116,6 +243,8 @@ static const char * const qusb2_phy_vreg_names[] = {
*
* @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;
@@ -132,6 +261,8 @@ struct qusb2_phy {
const struct qusb2_phy_cfg *cfg;
bool has_se_clk_scheme;
+ bool phy_initialized;
+ enum phy_mode mode;
};
static inline void qusb2_setbits(void __iomem *base, u32 offset, u32 val)
@@ -160,26 +291,32 @@ static inline void qusb2_clrbits(void __iomem *base, u32 offset, u32 val)
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++)
- writel(tbl[i].val, base + tbl[i].offset);
+ 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);
+ }
}
/*
* Fetches HS Tx tuning value from nvmem and sets the
- * QUSB2PHY_PORT_TUNE2 register.
+ * 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;
/*
- * Read efuse register having TUNE2 parameter's high nibble.
+ * Read efuse register having TUNE2/1 parameter's high nibble.
* If efuse register shows value as 0x0, or if we fail to find
* a valid efuse register settings, then use default value
* as 0xB for high nibble that we have already set while
@@ -191,58 +328,169 @@ static void qusb2_phy_set_tune2_param(struct qusb2_phy *qphy)
return;
}
- /* Fused TUNE2 value is the higher nibble only */
- qusb2_setbits(qphy->base, QUSB2PHY_PORT_TUNE2, val[0] << 0x4);
+ /* Fused TUNE1/2 value is the higher nibble only */
+ if (cfg->update_tune1_with_efuse)
+ qusb2_setbits(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE1],
+ val[0] << 0x4);
+ else
+ qusb2_setbits(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE2],
+ val[0] << 0x4);
+
}
-static int qusb2_phy_poweron(struct phy *phy)
+static int qusb2_phy_set_mode(struct phy *phy, enum phy_mode mode)
{
struct qusb2_phy *qphy = phy_get_drvdata(phy);
- int num = ARRAY_SIZE(qphy->vregs);
+
+ 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(&phy->dev, "%s(): Powering-on QUSB2 phy\n", __func__);
+ dev_vdbg(dev, "Resuming QUSB2 phy, mode:%d\n", qphy->mode);
- /* turn on regulator supplies */
- ret = regulator_bulk_enable(num, qphy->vregs);
- if (ret)
- return ret;
+ 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(&phy->dev, "failed to enable iface_clk, %d\n", ret);
- regulator_bulk_disable(num, qphy->vregs);
+ dev_err(dev, "failed to enable iface_clk, %d\n", ret);
return ret;
}
- return 0;
-}
+ 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;
+ }
-static int qusb2_phy_poweroff(struct phy *phy)
-{
- struct qusb2_phy *qphy = phy_get_drvdata(phy);
+ if (!qphy->has_se_clk_scheme) {
+ clk_prepare_enable(qphy->ref_clk);
+ if (ret) {
+ dev_err(dev, "failed to enable ref clk, %d\n", ret);
+ goto disable_ahb_clk;
+ }
+ }
- clk_disable_unprepare(qphy->iface_clk);
+ writel(0x0, qphy->base + cfg->regs[QUSB2PHY_INTR_CTRL]);
- regulator_bulk_disable(ARRAY_SIZE(qphy->vregs), qphy->vregs);
+ /* 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);
- unsigned int val;
+ 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);
- return ret;
+ goto disable_iface_clk;
}
/* Perform phy reset */
@@ -262,20 +510,23 @@ static int qusb2_phy_init(struct phy *phy)
}
/* Disable the PHY */
- qusb2_setbits(qphy->base, QUSB2PHY_PORT_POWERDOWN,
- CLAMP_N_EN | FREEZIO_N | POWER_DOWN);
+ qusb2_setbits(qphy->base, cfg->regs[QUSB2PHY_PORT_POWERDOWN],
+ qphy->cfg->disable_ctrl);
- /* save reset value to override reference clock scheme later */
- val = readl(qphy->base + QUSB2PHY_PLL_TEST);
+ 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, qphy->cfg->tbl,
- qphy->cfg->tbl_num);
+ qcom_qusb2_phy_configure(qphy->base, cfg->regs, cfg->tbl,
+ cfg->tbl_num);
/* Set efuse value for tuning the PHY */
qusb2_phy_set_tune2_param(qphy);
/* Enable the PHY */
- qusb2_clrbits(qphy->base, QUSB2PHY_PORT_POWERDOWN, POWER_DOWN);
+ qusb2_clrbits(qphy->base, cfg->regs[QUSB2PHY_PORT_POWERDOWN],
+ POWER_DOWN);
/* Required to get phy pll lock successfully */
usleep_range(150, 160);
@@ -308,32 +559,37 @@ static int qusb2_phy_init(struct phy *phy)
}
if (!qphy->has_se_clk_scheme) {
- val &= ~CLK_REF_SEL;
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;
}
- } else {
- val |= CLK_REF_SEL;
}
- writel(val, qphy->base + QUSB2PHY_PLL_TEST);
+ if (cfg->has_pll_test) {
+ if (!qphy->has_se_clk_scheme)
+ val &= ~CLK_REF_SEL;
+ else
+ val |= CLK_REF_SEL;
- /* ensure above write is through */
- readl(qphy->base + QUSB2PHY_PLL_TEST);
+ 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 + QUSB2PHY_PLL_STATUS);
- if (!(val & PLL_LOCKED)) {
+ 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;
@@ -344,6 +600,11 @@ 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;
}
@@ -352,8 +613,8 @@ static int qusb2_phy_exit(struct phy *phy)
struct qusb2_phy *qphy = phy_get_drvdata(phy);
/* Disable the PHY */
- qusb2_setbits(qphy->base, QUSB2PHY_PORT_POWERDOWN,
- CLAMP_N_EN | FREEZIO_N | POWER_DOWN);
+ 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);
@@ -361,6 +622,11 @@ static int qusb2_phy_exit(struct phy *phy)
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;
}
@@ -368,8 +634,7 @@ static int qusb2_phy_exit(struct phy *phy)
static const struct phy_ops qusb2_phy_gen_ops = {
.init = qusb2_phy_init,
.exit = qusb2_phy_exit,
- .power_on = qusb2_phy_poweron,
- .power_off = qusb2_phy_poweroff,
+ .set_mode = qusb2_phy_set_mode,
.owner = THIS_MODULE,
};
@@ -377,11 +642,19 @@ static const struct of_device_id qusb2_phy_of_match_table[] = {
{
.compatible = "qcom,msm8996-qusb2-phy",
.data = &msm8996_phy_cfg,
+ }, {
+ .compatible = "qcom,qusb2-v2-phy",
+ .data = &qusb2_v2_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;
@@ -459,11 +732,19 @@ static int qusb2_phy_probe(struct platform_device *pdev)
qphy->cell = NULL;
dev_dbg(dev, "failed to lookup tune2 hstx trim value\n");
}
+ 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;
@@ -474,6 +755,8 @@ static int qusb2_phy_probe(struct platform_device *pdev)
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);
}
@@ -482,6 +765,7 @@ 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,
},
};
diff --git a/drivers/phy/ralink/Kconfig b/drivers/phy/ralink/Kconfig
index b17635b407bc..14fd219535ef 100644
--- a/drivers/phy/ralink/Kconfig
+++ b/drivers/phy/ralink/Kconfig
@@ -4,6 +4,7 @@
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
diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c
index 9c90e7d67e0a..fb8f05e39cf7 100644
--- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c
+++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c
@@ -397,6 +397,10 @@ static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = {
.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",
},
{ }
diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig
index f5325b2b679e..0e15119ddfc6 100644
--- a/drivers/phy/rockchip/Kconfig
+++ b/drivers/phy/rockchip/Kconfig
@@ -29,6 +29,7 @@ config PHY_ROCKCHIP_INNO_USB2
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
diff --git a/drivers/phy/rockchip/phy-rockchip-emmc.c b/drivers/phy/rockchip/phy-rockchip-emmc.c
index f1b24f18e9b2..b237360f95f6 100644
--- a/drivers/phy/rockchip/phy-rockchip-emmc.c
+++ b/drivers/phy/rockchip/phy-rockchip-emmc.c
@@ -76,6 +76,13 @@
#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;
@@ -89,7 +96,7 @@ static int rockchip_emmc_phy_power(struct phy *phy, bool on_off)
unsigned int dllrdy;
unsigned int freqsel = PHYCTRL_FREQSEL_200M;
unsigned long rate;
- unsigned long timeout;
+ int ret;
/*
* Keep phyctrl_pdb and phyctrl_endll low to allow
@@ -160,17 +167,19 @@ static int rockchip_emmc_phy_power(struct phy *phy, bool on_off)
PHYCTRL_PDB_SHIFT));
/*
- * According to the user manual, it asks driver to
- * wait 5us for calpad busy trimming
+ * 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.
*/
- udelay(5);
- regmap_read(rk_phy->reg_base,
- rk_phy->reg_offset + GRF_EMMCPHY_STATUS,
- &caldone);
- caldone = (caldone >> PHYCTRL_CALDONE_SHIFT) & PHYCTRL_CALDONE_MASK;
- if (caldone != PHYCTRL_CALDONE_DONE) {
- pr_err("rockchip_emmc_phy_power: caldone timeout.\n");
- return -ETIMEDOUT;
+ 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 */
@@ -210,28 +219,15 @@ static int rockchip_emmc_phy_power(struct phy *phy, bool on_off)
* 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. We still busy wait here because:
- * - In most cases it should be super fast.
- * - This is not called lots during normal operation so it shouldn't
- * be a power or performance problem to busy wait. We expect it
- * only at boot / resume. In both cases, eMMC is probably on the
- * critical path so busy waiting a little extra time should be OK.
+ * generous and give it 50ms.
*/
- timeout = jiffies + msecs_to_jiffies(50);
- do {
- udelay(1);
-
- regmap_read(rk_phy->reg_base,
- rk_phy->reg_offset + GRF_EMMCPHY_STATUS,
- &dllrdy);
- dllrdy = (dllrdy >> PHYCTRL_DLLRDY_SHIFT) & PHYCTRL_DLLRDY_MASK;
- if (dllrdy == PHYCTRL_DLLRDY_DONE)
- break;
- } while (!time_after(jiffies, timeout));
-
- if (dllrdy != PHYCTRL_DLLRDY_DONE) {
- pr_err("rockchip_emmc_phy_power: dllrdy timeout.\n");
- return -ETIMEDOUT;
+ 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;
diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c
index 7492c8978217..76a4b58ec771 100644
--- a/drivers/phy/rockchip/phy-rockchip-typec.c
+++ b/drivers/phy/rockchip/phy-rockchip-typec.c
@@ -355,11 +355,26 @@ struct usb3phy_reg {
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 {
@@ -372,7 +387,7 @@ struct rockchip_typec_phy {
struct reset_control *uphy_rst;
struct reset_control *pipe_rst;
struct reset_control *tcphy_rst;
- struct rockchip_usb3phy_port_cfg port_cfgs;
+ const struct rockchip_usb3phy_port_cfg *port_cfgs;
/* mutex to protect access to individual PHYs */
struct mutex lock;
@@ -424,6 +439,30 @@ struct phy_reg dp_pll_cfg[] = {
{ 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;
@@ -691,7 +730,7 @@ static void tcphy_dp_aux_calibration(struct rockchip_typec_phy *tcphy)
static int tcphy_phy_init(struct rockchip_typec_phy *tcphy, u8 mode)
{
- struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs;
+ const struct rockchip_usb3phy_port_cfg *cfg = tcphy->port_cfgs;
int ret, i;
u32 val;
@@ -782,6 +821,9 @@ static int tcphy_get_mode(struct rockchip_typec_phy *tcphy)
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);
@@ -818,10 +860,22 @@ static int tcphy_get_mode(struct rockchip_typec_phy *tcphy)
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);
- struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs;
+ 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;
@@ -835,8 +889,10 @@ static int rockchip_usb3_phy_power_on(struct phy *phy)
}
/* DP-only mode; fall back to USB2 */
- if (!(new_mode & (MODE_DFP_USB | MODE_UFP_USB)))
+ 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;
@@ -852,6 +908,9 @@ static int rockchip_usb3_phy_power_on(struct phy *phy)
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);
@@ -872,6 +931,7 @@ 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;
@@ -894,6 +954,7 @@ static const struct phy_ops rockchip_usb3_phy_ops = {
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;
@@ -926,6 +987,8 @@ static int rockchip_dp_phy_power_on(struct phy *phy)
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);
@@ -984,51 +1047,9 @@ static const struct phy_ops rockchip_dp_phy_ops = {
.owner = THIS_MODULE,
};
-static int tcphy_get_param(struct device *dev,
- struct usb3phy_reg *reg,
- const char *name)
-{
- u32 buffer[3];
- int ret;
-
- ret = of_property_read_u32_array(dev->of_node, name, buffer, 3);
- if (ret) {
- dev_err(dev, "Can not parse %s\n", name);
- return ret;
- }
-
- reg->offset = buffer[0];
- reg->enable_bit = buffer[1];
- reg->write_enable = buffer[2];
- return 0;
-}
-
static int tcphy_parse_dt(struct rockchip_typec_phy *tcphy,
struct device *dev)
{
- struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs;
- int ret;
-
- ret = tcphy_get_param(dev, &cfg->typec_conn_dir,
- "rockchip,typec-conn-dir");
- if (ret)
- return ret;
-
- ret = tcphy_get_param(dev, &cfg->usb3tousb2_en,
- "rockchip,usb3tousb2-en");
- if (ret)
- return ret;
-
- ret = tcphy_get_param(dev, &cfg->external_psm,
- "rockchip,external-psm");
- if (ret)
- return ret;
-
- ret = tcphy_get_param(dev, &cfg->pipe_status,
- "rockchip,pipe-status");
- if (ret)
- return ret;
-
tcphy->grf_regs = syscon_regmap_lookup_by_phandle(dev->of_node,
"rockchip,grf");
if (IS_ERR(tcphy->grf_regs)) {
@@ -1071,7 +1092,7 @@ static int tcphy_parse_dt(struct rockchip_typec_phy *tcphy,
static void typec_phy_pre_init(struct rockchip_typec_phy *tcphy)
{
- struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs;
+ const struct rockchip_usb3phy_port_cfg *cfg = tcphy->port_cfgs;
reset_control_assert(tcphy->tcphy_rst);
reset_control_assert(tcphy->uphy_rst);
@@ -1092,17 +1113,43 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev)
struct rockchip_typec_phy *tcphy;
struct phy_provider *phy_provider;
struct resource *res;
- int ret;
+ 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;
@@ -1115,9 +1162,13 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev)
tcphy->extcon = extcon_get_edev_by_phandle(dev, 0);
if (IS_ERR(tcphy->extcon)) {
- if (PTR_ERR(tcphy->extcon) != -EPROBE_DEFER)
- dev_err(dev, "Invalid or missing extcon\n");
- return PTR_ERR(tcphy->extcon);
+ 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);
@@ -1162,8 +1213,11 @@ static int rockchip_typec_phy_remove(struct platform_device *pdev)
}
static const struct of_device_id rockchip_typec_phy_dt_ids[] = {
- { .compatible = "rockchip,rk3399-typec-phy" },
- {}
+ {
+ .compatible = "rockchip,rk3399-typec-phy",
+ .data = &rk3399_usb3phy_port_cfgs
+ },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_typec_phy_dt_ids);
diff --git a/drivers/phy/samsung/Kconfig b/drivers/phy/samsung/Kconfig
index b7e0645a7bd9..2a5d33cb0e7e 100644
--- a/drivers/phy/samsung/Kconfig
+++ b/drivers/phy/samsung/Kconfig
@@ -49,7 +49,7 @@ config PHY_EXYNOS4210_USB2
config PHY_EXYNOS4X12_USB2
bool
depends on PHY_SAMSUNG_USB2
- default SOC_EXYNOS3250 || SOC_EXYNOS4212 || SOC_EXYNOS4412
+ default SOC_EXYNOS3250 || SOC_EXYNOS4412
config PHY_EXYNOS5250_USB2
bool
diff --git a/drivers/phy/st/Kconfig b/drivers/phy/st/Kconfig
index 0814d3f87ec6..609719bdfa50 100644
--- a/drivers/phy/st/Kconfig
+++ b/drivers/phy/st/Kconfig
@@ -31,3 +31,17 @@ config PHY_STIH407_USB
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
index e2adfe2166d2..c0091ad1fd48 100644
--- a/drivers/phy/st/Makefile
+++ b/drivers/phy/st/Makefile
@@ -2,3 +2,4 @@ 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-stm32-usbphyc.c b/drivers/phy/st/phy-stm32-usbphyc.c
new file mode 100644
index 000000000000..bc4e78a19c91
--- /dev/null
+++ b/drivers/phy/st/phy-stm32-usbphyc.c
@@ -0,0 +1,461 @@
+// 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;
+ bool pll_enabled;
+};
+
+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", i, 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/xusb-tegra210.c b/drivers/phy/tegra/xusb-tegra210.c
index 9d0689ebd28c..05bee32a3a4d 100644
--- a/drivers/phy/tegra/xusb-tegra210.c
+++ b/drivers/phy/tegra/xusb-tegra210.c
@@ -169,6 +169,7 @@
#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
@@ -537,11 +538,8 @@ static int tegra210_sata_uphy_enable(struct tegra_xusb_padctl *padctl, bool usb)
value |= (XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SATA_VAL <<
XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT);
- /* XXX PLL0_XDIGCLK_EN */
- /*
- value &= ~(1 << 19);
+ 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 <<
diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c
index 63e916d4d069..11aa5902a9ac 100644
--- a/drivers/phy/tegra/xusb.c
+++ b/drivers/phy/tegra/xusb.c
@@ -418,7 +418,7 @@ tegra_xusb_port_find_lane(struct tegra_xusb_port *port,
{
struct tegra_xusb_lane *lane, *match = ERR_PTR(-ENODEV);
- for (map = map; map->type; map++) {
+ for (; map->type; map++) {
if (port->index != map->port)
continue;