]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
phy: Add support for drivers to enable USB on QCS404 SoC
authorSumit Garg <sumit.garg@linaro.org>
Thu, 4 Aug 2022 14:27:10 +0000 (19:57 +0530)
committerTom Rini <trini@konsulko.com>
Fri, 26 Aug 2022 14:55:45 +0000 (10:55 -0400)
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>
drivers/phy/qcom/Kconfig
drivers/phy/qcom/Makefile
drivers/phy/qcom/phy-qcom-usb-hs-28nm.c [new file with mode: 0644]
drivers/phy/qcom/phy-qcom-usb-ss.c [new file with mode: 0644]

index f685a6455e997d559ed7e8c5ac0699aef524bd17..f4ca174805a4f21e7dc002590cc2668df6ccb4ae 100644 (file)
@@ -11,3 +11,19 @@ config PHY_QCOM_IPQ4019_USB
        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.
index 4a340e33c8b69f7892de60923145ed843f28204b..2113f178c0c7ca7c57057d1aa514912b368cc028 100644 (file)
@@ -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 (file)
index 0000000..14c3d83
--- /dev/null
@@ -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 (file)
index 0000000..4e81687
--- /dev/null
@@ -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),
+};