diff mbox series

[02/13] phy: Add support for drivers to enable USB on QCS404 SoC

Message ID 20220804142721.536556-3-sumit.garg@linaro.org
State New
Headers show
Series USB support for QCS404 SoC | expand

Commit Message

Sumit Garg Aug. 4, 2022, 2:27 p.m. UTC
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                |  16 ++
 drivers/phy/qcom/Makefile               |   2 +
 drivers/phy/qcom/phy-qcom-usb-hs-28nm.c | 250 ++++++++++++++++++++++++
 drivers/phy/qcom/phy-qcom-usb-ss.c      | 154 +++++++++++++++
 4 files changed, 422 insertions(+)
 create mode 100644 drivers/phy/qcom/phy-qcom-usb-hs-28nm.c
 create mode 100644 drivers/phy/qcom/phy-qcom-usb-ss.c

Comments

Tom Rini Aug. 26, 2022, 6:50 p.m. UTC | #1
On Thu, Aug 04, 2022 at 07:57:10PM +0530, Sumit Garg wrote:

> 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>

Applied to u-boot/next, thanks!
diff mbox series

Patch

diff --git a/drivers/phy/qcom/Kconfig b/drivers/phy/qcom/Kconfig
index f685a6455e..f4ca174805 100644
--- a/drivers/phy/qcom/Kconfig
+++ b/drivers/phy/qcom/Kconfig
@@ -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.
diff --git a/drivers/phy/qcom/Makefile b/drivers/phy/qcom/Makefile
index 4a340e33c8..2113f178c0 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 0000000000..14c3d8394d
--- /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 0000000000..4e816879c6
--- /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),
+};