Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

phy: qualcomm: usb: Add SuperSpeed PHY driver

Controls Qualcomm's SS PHY 1.0.0 implemented on various SoCs on both the
20nm and 28nm process nodes.

Based on Sriharsha Allenki's <sallenki@codeaurora.org> original code.

[bod: Removed dependency on extcon.
Switched to gpio-usb-conn to handle VBUS On/Off
Switched to usb-role-switch to bind gpio-usb-conn to DWC3]
Signed-off-by: Jorge Ramirez-Ortiz <jorge.ramirez-ortiz@linaro.org>
Cc: Jorge Ramirez-Ortiz <jorge.ramirez.ortiz@gmail.com>
Cc: Sriharsha Allenki's <sallenki@codeaurora.org>
Cc: Andy Gross <agross@kernel.org>
Cc: Bjorn Andersson <bjorn.andersson@linaro.org>
Cc: Kishon Vijay Abraham I <kishon@ti.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: linux-arm-msm@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Reviewed-by: Philipp Zabel <p.zabel@pengutronix.de>
Tested-by: Bjorn Andersson <bjorn.andersson@linaro.org>
Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>

authored by

Jorge Ramirez-Ortiz and committed by
Kishon Vijay Abraham I
6076967a 8563ec5a

+256
+9
drivers/phy/qualcomm/Kconfig
··· 102 102 High-Speed PHY driver. This driver supports the Hi-Speed PHY which 103 103 is usually paired with either the ChipIdea or Synopsys DWC3 USB 104 104 IPs on MSM SOCs. 105 + 106 + config PHY_QCOM_USB_SS 107 + tristate "Qualcomm USB Super-Speed PHY driver" 108 + depends on ARCH_QCOM || COMPILE_TEST 109 + depends on EXTCON || !EXTCON # if EXTCON=m, this cannot be built-in 110 + select GENERIC_PHY 111 + help 112 + Enable this to support the Super-Speed USB transceiver on various 113 + Qualcomm chipsets.
+1
drivers/phy/qualcomm/Makefile
··· 11 11 obj-$(CONFIG_PHY_QCOM_USB_HS) += phy-qcom-usb-hs.o 12 12 obj-$(CONFIG_PHY_QCOM_USB_HSIC) += phy-qcom-usb-hsic.o 13 13 obj-$(CONFIG_PHY_QCOM_USB_HS_28NM) += phy-qcom-usb-hs-28nm.o 14 + obj-$(CONFIG_PHY_QCOM_USB_SS) += phy-qcom-usb-ss.o
+246
drivers/phy/qualcomm/phy-qcom-usb-ss.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Copyright (c) 2012-2014,2017 The Linux Foundation. All rights reserved. 4 + * Copyright (c) 2018-2020, Linaro Limited 5 + */ 6 + 7 + #include <linux/clk.h> 8 + #include <linux/delay.h> 9 + #include <linux/err.h> 10 + #include <linux/io.h> 11 + #include <linux/kernel.h> 12 + #include <linux/module.h> 13 + #include <linux/of.h> 14 + #include <linux/phy/phy.h> 15 + #include <linux/platform_device.h> 16 + #include <linux/regulator/consumer.h> 17 + #include <linux/reset.h> 18 + #include <linux/slab.h> 19 + 20 + #define PHY_CTRL0 0x6C 21 + #define PHY_CTRL1 0x70 22 + #define PHY_CTRL2 0x74 23 + #define PHY_CTRL4 0x7C 24 + 25 + /* PHY_CTRL bits */ 26 + #define REF_PHY_EN BIT(0) 27 + #define LANE0_PWR_ON BIT(2) 28 + #define SWI_PCS_CLK_SEL BIT(4) 29 + #define TST_PWR_DOWN BIT(4) 30 + #define PHY_RESET BIT(7) 31 + 32 + #define NUM_BULK_CLKS 3 33 + #define NUM_BULK_REGS 2 34 + 35 + struct ssphy_priv { 36 + void __iomem *base; 37 + struct device *dev; 38 + struct reset_control *reset_com; 39 + struct reset_control *reset_phy; 40 + struct regulator_bulk_data regs[NUM_BULK_REGS]; 41 + struct clk_bulk_data clks[NUM_BULK_CLKS]; 42 + enum phy_mode mode; 43 + }; 44 + 45 + static inline void qcom_ssphy_updatel(void __iomem *addr, u32 mask, u32 val) 46 + { 47 + writel((readl(addr) & ~mask) | val, addr); 48 + } 49 + 50 + static int qcom_ssphy_do_reset(struct ssphy_priv *priv) 51 + { 52 + int ret; 53 + 54 + if (!priv->reset_com) { 55 + qcom_ssphy_updatel(priv->base + PHY_CTRL1, PHY_RESET, 56 + PHY_RESET); 57 + usleep_range(10, 20); 58 + qcom_ssphy_updatel(priv->base + PHY_CTRL1, PHY_RESET, 0); 59 + } else { 60 + ret = reset_control_assert(priv->reset_com); 61 + if (ret) { 62 + dev_err(priv->dev, "Failed to assert reset com\n"); 63 + return ret; 64 + } 65 + 66 + ret = reset_control_assert(priv->reset_phy); 67 + if (ret) { 68 + dev_err(priv->dev, "Failed to assert reset phy\n"); 69 + return ret; 70 + } 71 + 72 + usleep_range(10, 20); 73 + 74 + ret = reset_control_deassert(priv->reset_com); 75 + if (ret) { 76 + dev_err(priv->dev, "Failed to deassert reset com\n"); 77 + return ret; 78 + } 79 + 80 + ret = reset_control_deassert(priv->reset_phy); 81 + if (ret) { 82 + dev_err(priv->dev, "Failed to deassert reset phy\n"); 83 + return ret; 84 + } 85 + } 86 + 87 + return 0; 88 + } 89 + 90 + static int qcom_ssphy_power_on(struct phy *phy) 91 + { 92 + struct ssphy_priv *priv = phy_get_drvdata(phy); 93 + int ret; 94 + 95 + ret = regulator_bulk_enable(NUM_BULK_REGS, priv->regs); 96 + if (ret) 97 + return ret; 98 + 99 + ret = clk_bulk_prepare_enable(NUM_BULK_CLKS, priv->clks); 100 + if (ret) 101 + goto err_disable_regulator; 102 + 103 + ret = qcom_ssphy_do_reset(priv); 104 + if (ret) 105 + goto err_disable_clock; 106 + 107 + writeb(SWI_PCS_CLK_SEL, priv->base + PHY_CTRL0); 108 + qcom_ssphy_updatel(priv->base + PHY_CTRL4, LANE0_PWR_ON, LANE0_PWR_ON); 109 + qcom_ssphy_updatel(priv->base + PHY_CTRL2, REF_PHY_EN, REF_PHY_EN); 110 + qcom_ssphy_updatel(priv->base + PHY_CTRL4, TST_PWR_DOWN, 0); 111 + 112 + return 0; 113 + err_disable_clock: 114 + clk_bulk_disable_unprepare(NUM_BULK_CLKS, priv->clks); 115 + err_disable_regulator: 116 + regulator_bulk_disable(NUM_BULK_REGS, priv->regs); 117 + 118 + return ret; 119 + } 120 + 121 + static int qcom_ssphy_power_off(struct phy *phy) 122 + { 123 + struct ssphy_priv *priv = phy_get_drvdata(phy); 124 + 125 + qcom_ssphy_updatel(priv->base + PHY_CTRL4, LANE0_PWR_ON, 0); 126 + qcom_ssphy_updatel(priv->base + PHY_CTRL2, REF_PHY_EN, 0); 127 + qcom_ssphy_updatel(priv->base + PHY_CTRL4, TST_PWR_DOWN, TST_PWR_DOWN); 128 + 129 + clk_bulk_disable_unprepare(NUM_BULK_CLKS, priv->clks); 130 + regulator_bulk_disable(NUM_BULK_REGS, priv->regs); 131 + 132 + return 0; 133 + } 134 + 135 + static int qcom_ssphy_init_clock(struct ssphy_priv *priv) 136 + { 137 + priv->clks[0].id = "ref"; 138 + priv->clks[1].id = "ahb"; 139 + priv->clks[2].id = "pipe"; 140 + 141 + return devm_clk_bulk_get(priv->dev, NUM_BULK_CLKS, priv->clks); 142 + } 143 + 144 + static int qcom_ssphy_init_regulator(struct ssphy_priv *priv) 145 + { 146 + int ret; 147 + 148 + priv->regs[0].supply = "vdd"; 149 + priv->regs[1].supply = "vdda1p8"; 150 + ret = devm_regulator_bulk_get(priv->dev, NUM_BULK_REGS, priv->regs); 151 + if (ret) { 152 + if (ret != -EPROBE_DEFER) 153 + dev_err(priv->dev, "Failed to get regulators\n"); 154 + return ret; 155 + } 156 + 157 + return ret; 158 + } 159 + 160 + static int qcom_ssphy_init_reset(struct ssphy_priv *priv) 161 + { 162 + priv->reset_com = devm_reset_control_get_optional_exclusive(priv->dev, "com"); 163 + if (IS_ERR(priv->reset_com)) { 164 + dev_err(priv->dev, "Failed to get reset control com\n"); 165 + return PTR_ERR(priv->reset_com); 166 + } 167 + 168 + if (priv->reset_com) { 169 + /* if reset_com is present, reset_phy is no longer optional */ 170 + priv->reset_phy = devm_reset_control_get_exclusive(priv->dev, "phy"); 171 + if (IS_ERR(priv->reset_phy)) { 172 + dev_err(priv->dev, "Failed to get reset control phy\n"); 173 + return PTR_ERR(priv->reset_phy); 174 + } 175 + } 176 + 177 + return 0; 178 + } 179 + 180 + static const struct phy_ops qcom_ssphy_ops = { 181 + .power_off = qcom_ssphy_power_off, 182 + .power_on = qcom_ssphy_power_on, 183 + .owner = THIS_MODULE, 184 + }; 185 + 186 + static int qcom_ssphy_probe(struct platform_device *pdev) 187 + { 188 + struct device *dev = &pdev->dev; 189 + struct phy_provider *provider; 190 + struct ssphy_priv *priv; 191 + struct phy *phy; 192 + int ret; 193 + 194 + priv = devm_kzalloc(dev, sizeof(struct ssphy_priv), GFP_KERNEL); 195 + if (!priv) 196 + return -ENOMEM; 197 + 198 + priv->dev = dev; 199 + priv->mode = PHY_MODE_INVALID; 200 + 201 + priv->base = devm_platform_ioremap_resource(pdev, 0); 202 + if (IS_ERR(priv->base)) 203 + return PTR_ERR(priv->base); 204 + 205 + ret = qcom_ssphy_init_clock(priv); 206 + if (ret) 207 + return ret; 208 + 209 + ret = qcom_ssphy_init_reset(priv); 210 + if (ret) 211 + return ret; 212 + 213 + ret = qcom_ssphy_init_regulator(priv); 214 + if (ret) 215 + return ret; 216 + 217 + phy = devm_phy_create(dev, dev->of_node, &qcom_ssphy_ops); 218 + if (IS_ERR(phy)) { 219 + dev_err(dev, "Failed to create the SS phy\n"); 220 + return PTR_ERR(phy); 221 + } 222 + 223 + phy_set_drvdata(phy, priv); 224 + 225 + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); 226 + 227 + return PTR_ERR_OR_ZERO(provider); 228 + } 229 + 230 + static const struct of_device_id qcom_ssphy_match[] = { 231 + { .compatible = "qcom,usb-ss-28nm-phy", }, 232 + { }, 233 + }; 234 + MODULE_DEVICE_TABLE(of, qcom_ssphy_match); 235 + 236 + static struct platform_driver qcom_ssphy_driver = { 237 + .probe = qcom_ssphy_probe, 238 + .driver = { 239 + .name = "qcom-usb-ssphy", 240 + .of_match_table = qcom_ssphy_match, 241 + }, 242 + }; 243 + module_platform_driver(qcom_ssphy_driver); 244 + 245 + MODULE_DESCRIPTION("Qualcomm SuperSpeed USB PHY driver"); 246 + MODULE_LICENSE("GPL v2");