phy: Add support for drivers to enable USB on QCS404 SoC
QCS404 SoC supports two types of PHY, one supports high speed mode or
USB2 PHY and the other supports super speed mode or USB3 PHY. So add
corresponding PHY drivers.
Signed-off-by: Sumit Garg <sumit.garg@linaro.org>
diff --git a/drivers/phy/qcom/Kconfig b/drivers/phy/qcom/Kconfig
index f685a64..f4ca174 100644
--- a/drivers/phy/qcom/Kconfig
+++ b/drivers/phy/qcom/Kconfig
@@ -11,3 +11,19 @@
depends on PHY && ARCH_IPQ40XX
help
Support for the USB PHY-s on Qualcomm IPQ40xx SoC-s.
+
+config PHY_QCOM_USB_HS_28NM
+ tristate "Qualcomm 28nm High-Speed PHY"
+ depends on PHY && ARCH_SNAPDRAGON
+ help
+ Enable this to support the Qualcomm Synopsys DesignWare Core 28nm
+ High-Speed PHY driver. This driver supports the Hi-Speed PHY which
+ is usually paired with either the ChipIdea or Synopsys DWC3 USB
+ IPs on MSM SOCs.
+
+config PHY_QCOM_USB_SS
+ tristate "Qualcomm USB Super-Speed PHY driver"
+ depends on PHY && ARCH_SNAPDRAGON
+ help
+ Enable this to support the Super-Speed USB transceiver on various
+ Qualcomm chipsets.
diff --git a/drivers/phy/qcom/Makefile b/drivers/phy/qcom/Makefile
index 4a340e3..2113f17 100644
--- a/drivers/phy/qcom/Makefile
+++ b/drivers/phy/qcom/Makefile
@@ -1,2 +1,4 @@
obj-$(CONFIG_PHY_QCOM_IPQ4019_USB) += phy-qcom-ipq4019-usb.o
obj-$(CONFIG_MSM8916_USB_PHY) += msm8916-usbh-phy.o
+obj-$(CONFIG_PHY_QCOM_USB_HS_28NM) += phy-qcom-usb-hs-28nm.o
+obj-$(CONFIG_PHY_QCOM_USB_SS) += phy-qcom-usb-ss.o
diff --git a/drivers/phy/qcom/phy-qcom-usb-hs-28nm.c b/drivers/phy/qcom/phy-qcom-usb-hs-28nm.c
new file mode 100644
index 0000000..14c3d83
--- /dev/null
+++ b/drivers/phy/qcom/phy-qcom-usb-hs-28nm.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2022 Sumit Garg <sumit.garg@linaro.org>
+ *
+ * Based on Linux driver
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <generic-phy.h>
+#include <reset.h>
+#include <clk.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+
+/* PHY register and bit definitions */
+#define PHY_CTRL_COMMON0 0x078
+#define SIDDQ BIT(2)
+
+struct hsphy_init_seq {
+ int offset;
+ int val;
+ int delay;
+};
+
+struct hsphy_data {
+ const struct hsphy_init_seq *init_seq;
+ unsigned int init_seq_num;
+};
+
+struct hsphy_priv {
+ void __iomem *base;
+ struct clk_bulk clks;
+ struct reset_ctl phy_rst;
+ struct reset_ctl por_rst;
+ const struct hsphy_data *data;
+};
+
+static int hsphy_power_on(struct phy *phy)
+{
+ struct hsphy_priv *priv = dev_get_priv(phy->dev);
+ u32 val;
+
+ val = readb(priv->base + PHY_CTRL_COMMON0);
+ val &= ~SIDDQ;
+ writeb(val, priv->base + PHY_CTRL_COMMON0);
+
+ return 0;
+}
+
+static int hsphy_power_off(struct phy *phy)
+{
+ struct hsphy_priv *priv = dev_get_priv(phy->dev);
+ u32 val;
+
+ val = readb(priv->base + PHY_CTRL_COMMON0);
+ val |= SIDDQ;
+ writeb(val, priv->base + PHY_CTRL_COMMON0);
+
+ return 0;
+}
+
+static int hsphy_reset(struct hsphy_priv *priv)
+{
+ int ret;
+
+ ret = reset_assert(&priv->phy_rst);
+ if (ret)
+ return ret;
+
+ udelay(10);
+
+ ret = reset_deassert(&priv->phy_rst);
+ if (ret)
+ return ret;
+
+ udelay(80);
+
+ return 0;
+}
+
+static void hsphy_init_sequence(struct hsphy_priv *priv)
+{
+ const struct hsphy_data *data = priv->data;
+ const struct hsphy_init_seq *seq;
+ int i;
+
+ /* Device match data is optional. */
+ if (!data)
+ return;
+
+ seq = data->init_seq;
+
+ for (i = 0; i < data->init_seq_num; i++, seq++) {
+ writeb(seq->val, priv->base + seq->offset);
+ if (seq->delay)
+ udelay(seq->delay);
+ }
+}
+
+static int hsphy_por_reset(struct hsphy_priv *priv)
+{
+ int ret;
+ u32 val;
+
+ ret = reset_assert(&priv->por_rst);
+ if (ret)
+ return ret;
+
+ /*
+ * The Femto PHY is POR reset in the following scenarios.
+ *
+ * 1. After overriding the parameter registers.
+ * 2. Low power mode exit from PHY retention.
+ *
+ * Ensure that SIDDQ is cleared before bringing the PHY
+ * out of reset.
+ */
+ val = readb(priv->base + PHY_CTRL_COMMON0);
+ val &= ~SIDDQ;
+ writeb(val, priv->base + PHY_CTRL_COMMON0);
+
+ /*
+ * As per databook, 10 usec delay is required between
+ * PHY POR assert and de-assert.
+ */
+ udelay(10);
+ ret = reset_deassert(&priv->por_rst);
+ if (ret)
+ return ret;
+
+ /*
+ * As per databook, it takes 75 usec for PHY to stabilize
+ * after the reset.
+ */
+ udelay(80);
+
+ return 0;
+}
+
+static int hsphy_clk_init(struct udevice *dev, struct hsphy_priv *priv)
+{
+ int ret;
+
+ ret = clk_get_bulk(dev, &priv->clks);
+ if (ret == -ENOSYS || ret == -ENOENT)
+ return 0;
+ if (ret)
+ return ret;
+
+ ret = clk_enable_bulk(&priv->clks);
+ if (ret) {
+ clk_release_bulk(&priv->clks);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int hsphy_init(struct phy *phy)
+{
+ struct hsphy_priv *priv = dev_get_priv(phy->dev);
+ int ret;
+
+ ret = hsphy_clk_init(phy->dev, priv);
+ if (ret)
+ return ret;
+
+ ret = hsphy_reset(priv);
+ if (ret)
+ return ret;
+
+ hsphy_init_sequence(priv);
+
+ hsphy_por_reset(priv);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int hsphy_probe(struct udevice *dev)
+{
+ struct hsphy_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ priv->base = (void *)dev_read_addr(dev);
+ if ((ulong)priv->base == FDT_ADDR_T_NONE)
+ return -EINVAL;
+
+ ret = reset_get_by_name(dev, "phy", &priv->phy_rst);
+ if (ret)
+ return ret;
+
+ ret = reset_get_by_name(dev, "por", &priv->por_rst);
+ if (ret)
+ return ret;
+
+ priv->data = (const struct hsphy_data *)dev_get_driver_data(dev);
+
+ return 0;
+}
+
+static struct phy_ops hsphy_ops = {
+ .power_on = hsphy_power_on,
+ .power_off = hsphy_power_off,
+ .init = hsphy_init,
+};
+
+/*
+ * The macro is used to define an initialization sequence. Each tuple
+ * is meant to program 'value' into phy register at 'offset' with 'delay'
+ * in us followed.
+ */
+#define HSPHY_INIT_CFG(o, v, d) { .offset = o, .val = v, .delay = d, }
+
+static const struct hsphy_init_seq init_seq_femtophy[] = {
+ HSPHY_INIT_CFG(0xc0, 0x01, 0),
+ HSPHY_INIT_CFG(0xe8, 0x0d, 0),
+ HSPHY_INIT_CFG(0x74, 0x12, 0),
+ HSPHY_INIT_CFG(0x98, 0x63, 0),
+ HSPHY_INIT_CFG(0x9c, 0x03, 0),
+ HSPHY_INIT_CFG(0xa0, 0x1d, 0),
+ HSPHY_INIT_CFG(0xa4, 0x03, 0),
+ HSPHY_INIT_CFG(0x8c, 0x23, 0),
+ HSPHY_INIT_CFG(0x78, 0x08, 0),
+ HSPHY_INIT_CFG(0x7c, 0xdc, 0),
+ HSPHY_INIT_CFG(0x90, 0xe0, 20),
+ HSPHY_INIT_CFG(0x74, 0x10, 0),
+ HSPHY_INIT_CFG(0x90, 0x60, 0),
+};
+
+static const struct hsphy_data data_femtophy = {
+ .init_seq = init_seq_femtophy,
+ .init_seq_num = ARRAY_SIZE(init_seq_femtophy),
+};
+
+static const struct udevice_id hsphy_ids[] = {
+ { .compatible = "qcom,usb-hs-28nm-femtophy", .data = (ulong)&data_femtophy },
+ { }
+};
+
+U_BOOT_DRIVER(qcom_usb_hs_28nm) = {
+ .name = "qcom-usb-hs-28nm",
+ .id = UCLASS_PHY,
+ .of_match = hsphy_ids,
+ .ops = &hsphy_ops,
+ .probe = hsphy_probe,
+ .priv_auto = sizeof(struct hsphy_priv),
+};
diff --git a/drivers/phy/qcom/phy-qcom-usb-ss.c b/drivers/phy/qcom/phy-qcom-usb-ss.c
new file mode 100644
index 0000000..4e81687
--- /dev/null
+++ b/drivers/phy/qcom/phy-qcom-usb-ss.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2022 Sumit Garg <sumit.garg@linaro.org>
+ *
+ * Based on Linux driver
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <generic-phy.h>
+#include <linux/bitops.h>
+#include <asm/io.h>
+#include <reset.h>
+#include <clk.h>
+#include <linux/delay.h>
+
+#define PHY_CTRL0 0x6C
+#define PHY_CTRL1 0x70
+#define PHY_CTRL2 0x74
+#define PHY_CTRL4 0x7C
+
+/* PHY_CTRL bits */
+#define REF_PHY_EN BIT(0)
+#define LANE0_PWR_ON BIT(2)
+#define SWI_PCS_CLK_SEL BIT(4)
+#define TST_PWR_DOWN BIT(4)
+#define PHY_RESET BIT(7)
+
+struct ssphy_priv {
+ void __iomem *base;
+ struct clk_bulk clks;
+ struct reset_ctl com_rst;
+ struct reset_ctl phy_rst;
+};
+
+static inline void ssphy_updatel(void __iomem *addr, u32 mask, u32 val)
+{
+ writel((readl(addr) & ~mask) | val, addr);
+}
+
+static int ssphy_do_reset(struct ssphy_priv *priv)
+{
+ int ret;
+
+ ret = reset_assert(&priv->com_rst);
+ if (ret)
+ return ret;
+
+ ret = reset_assert(&priv->phy_rst);
+ if (ret)
+ return ret;
+
+ udelay(10);
+
+ ret = reset_deassert(&priv->com_rst);
+ if (ret)
+ return ret;
+
+ ret = reset_deassert(&priv->phy_rst);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ssphy_power_on(struct phy *phy)
+{
+ struct ssphy_priv *priv = dev_get_priv(phy->dev);
+ int ret;
+
+ ret = ssphy_do_reset(priv);
+ if (ret)
+ return ret;
+
+ writeb(SWI_PCS_CLK_SEL, priv->base + PHY_CTRL0);
+ ssphy_updatel(priv->base + PHY_CTRL4, LANE0_PWR_ON, LANE0_PWR_ON);
+ ssphy_updatel(priv->base + PHY_CTRL2, REF_PHY_EN, REF_PHY_EN);
+ ssphy_updatel(priv->base + PHY_CTRL4, TST_PWR_DOWN, 0);
+
+ return 0;
+}
+
+static int ssphy_power_off(struct phy *phy)
+{
+ struct ssphy_priv *priv = dev_get_priv(phy->dev);
+
+ ssphy_updatel(priv->base + PHY_CTRL4, LANE0_PWR_ON, 0);
+ ssphy_updatel(priv->base + PHY_CTRL2, REF_PHY_EN, 0);
+ ssphy_updatel(priv->base + PHY_CTRL4, TST_PWR_DOWN, TST_PWR_DOWN);
+
+ return 0;
+}
+
+static int ssphy_clk_init(struct udevice *dev, struct ssphy_priv *priv)
+{
+ int ret;
+
+ ret = clk_get_bulk(dev, &priv->clks);
+ if (ret == -ENOSYS || ret == -ENOENT)
+ return 0;
+ if (ret)
+ return ret;
+
+ ret = clk_enable_bulk(&priv->clks);
+ if (ret) {
+ clk_release_bulk(&priv->clks);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ssphy_probe(struct udevice *dev)
+{
+ struct ssphy_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ priv->base = (void *)dev_read_addr(dev);
+ if ((ulong)priv->base == FDT_ADDR_T_NONE)
+ return -EINVAL;
+
+ ret = ssphy_clk_init(dev, priv);
+ if (ret)
+ return ret;
+
+ ret = reset_get_by_name(dev, "com", &priv->com_rst);
+ if (ret)
+ return ret;
+
+ ret = reset_get_by_name(dev, "phy", &priv->phy_rst);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static struct phy_ops ssphy_ops = {
+ .power_on = ssphy_power_on,
+ .power_off = ssphy_power_off,
+};
+
+static const struct udevice_id ssphy_ids[] = {
+ { .compatible = "qcom,usb-ss-28nm-phy" },
+ { }
+};
+
+U_BOOT_DRIVER(qcom_usb_ss) = {
+ .name = "qcom-usb-ss",
+ .id = UCLASS_PHY,
+ .of_match = ssphy_ids,
+ .ops = &ssphy_ops,
+ .probe = ssphy_probe,
+ .priv_auto = sizeof(struct ssphy_priv),
+};