Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > linux.kernel > #1595814 > unrolled thread

[PATCH v5 2/4] phy: qcom-qusb2: New driver for QUSB2 PHY on Qcom chips

Started byVivek Gautam <vivek.gautam@codeaurora.org>
First post2017-03-09 10:10 +0100
Last post2017-03-10 09:50 +0100
Articles 3 — 2 participants

Back to article view | Back to linux.kernel

This discussion starts older than the indexed window; earlier articles aren't shown. The article labeled Started by below is the oldest one visible, not the original post.


Contents

  [PATCH v5 2/4] phy: qcom-qusb2: New driver for QUSB2 PHY on Qcom chips Vivek Gautam <vivek.gautam@codeaurora.org> - 2017-03-09 10:10 +0100
    Re: [PATCH v5 2/4] phy: qcom-qusb2: New driver for QUSB2 PHY on Qcom  chips Bjorn Andersson <bjorn.andersson@linaro.org> - 2017-03-09 12:20 +0100
      Re: [PATCH v5 2/4] phy: qcom-qusb2: New driver for QUSB2 PHY on Qcom  chips Vivek Gautam <vivek.gautam@codeaurora.org> - 2017-03-10 09:50 +0100

#1595814 — [PATCH v5 2/4] phy: qcom-qusb2: New driver for QUSB2 PHY on Qcom chips

FromVivek Gautam <vivek.gautam@codeaurora.org>
Date2017-03-09 10:10 +0100
Subject[PATCH v5 2/4] phy: qcom-qusb2: New driver for QUSB2 PHY on Qcom chips
Message-ID<tj2f8-35K-21@gated-at.bofh.it>
PHY transceiver driver for QUSB2 phy controller that provides
HighSpeed functionality for DWC3 controller present on
Qualcomm chipsets.

Signed-off-by: Vivek Gautam <vivek.gautam@codeaurora.org>
Reviewed-by: Stephen Boyd <sboyd@codeaurora.org>
---

Changes since v4:
 - Updated the copyright year to 2017.
 - Removed unnecessary of_match_ptr() cast for the match table,
   since the driver is compiled for CONFIG_OF.

Changes since v3:
 - Added 'Reviewed-by' from Stephen.
 - Fixed debug message for qusb2_phy_set_tune2_param().
 - Replaced devm_reset_control_get() with devm_reset_control_get_by_index()
   since we are requesting only one reset.
 - Updated devm_nvmem_cell_get() with a NULL cell id.
 - Made error labels more idiomatic.
 - Refactored qusb2_setbits() and qusb2_clrbits() a little bit to accept
   base address and register offset as two separate arguments.

Changes since v2:
 - Removed selecting 'RESET_CONTROLLER' config.
 - Added error handling for clk_prepare_enable paths.
 - Removed explicitly setting ref_clk rate to 19.2 MHz. Don't need to
   do that since 'xo' is modeled as parent to this clock.
 - Removed 'ref_clk_src' handling. Driver doesn't need to request and
   handle this clock.
 - Moved nvmem_cell_get() to probe function.
 - Simplified phy pll status handling.
 - Using of_device_get_match_data() to get match data.
 - Uniformly using lowercase for hex numbers.
 - Fixed sparse warnings.
 - Using shorter variable names in structure and in functions.
 - Handling various comment style shortcomings.

Changes since v1:
 - removed reference to clk_enabled/pwr_enabled.
 - moved clock and regulator enable code to phy_power_on/off() callbacks.
 - fixed return on EPROBE_DEFER in qusb2_phy_probe().
 - fixed phy create and phy register ordering.
 - removed references to non-lkml links from commit message.
 - took care of other minor nits.
 - Fixed coccinelle warnings -
   'PTR_ERR applied after initialization to constant'
 - Addressed review comment, regarding qfprom access for tune2 param value.
   This driver is now based on qfprom patch[1] that allows byte access now.

 drivers/phy/Kconfig          |  10 +
 drivers/phy/Makefile         |   1 +
 drivers/phy/phy-qcom-qusb2.c | 539 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 550 insertions(+)
 create mode 100644 drivers/phy/phy-qcom-qusb2.c

diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index dc5277ad1b5a..ccc9178e32cd 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -439,6 +439,16 @@ config PHY_STIH407_USB
 	  Enable this support to enable the picoPHY device used by USB2
 	  and USB3 controllers on STMicroelectronics STiH407 SoC families.
 
+config PHY_QCOM_QUSB2
+	tristate "Qualcomm QUSB2 PHY Driver"
+	depends on OF && (ARCH_QCOM || COMPILE_TEST)
+	select GENERIC_PHY
+	help
+	  Enable this to support the HighSpeed QUSB2 PHY transceiver for USB
+	  controllers on Qualcomm chips. This driver supports the high-speed
+	  PHY which is usually paired with either the ChipIdea or Synopsys DWC3
+	  USB IPs on MSM SOCs.
+
 config PHY_QCOM_UFS
 	tristate "Qualcomm UFS PHY driver"
 	depends on OF && ARCH_QCOM
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index e7b0feb1e125..bf16deff2a7b 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -50,6 +50,7 @@ obj-$(CONFIG_PHY_ST_SPEAR1310_MIPHY)	+= phy-spear1310-miphy.o
 obj-$(CONFIG_PHY_ST_SPEAR1340_MIPHY)	+= phy-spear1340-miphy.o
 obj-$(CONFIG_PHY_XGENE)			+= phy-xgene.o
 obj-$(CONFIG_PHY_STIH407_USB)		+= phy-stih407-usb.o
+obj-$(CONFIG_PHY_QCOM_QUSB2) 	+= phy-qcom-qusb2.o
 obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs.o
 obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs-qmp-20nm.o
 obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs-qmp-14nm.o
diff --git a/drivers/phy/phy-qcom-qusb2.c b/drivers/phy/phy-qcom-qusb2.c
new file mode 100644
index 000000000000..2879949ab1cd
--- /dev/null
+++ b/drivers/phy/phy-qcom-qusb2.c
@@ -0,0 +1,539 @@
+/*
+ * 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>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+
+#define QUSB2PHY_PLL_TEST		0x04
+#define CLK_REF_SEL			BIT(7)
+
+#define QUSB2PHY_PLL_TUNE		0x08
+#define QUSB2PHY_PLL_USER_CTL1		0x0c
+#define QUSB2PHY_PLL_USER_CTL2		0x10
+#define QUSB2PHY_PLL_AUTOPGM_CTL1	0x1c
+#define QUSB2PHY_PLL_PWR_CTRL		0x18
+
+#define QUSB2PHY_PLL_STATUS		0x38
+#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
+
+#define QUSB2PHY_PORT_POWERDOWN		0xb4
+#define CLAMP_N_EN			BIT(5)
+#define FREEZIO_N			BIT(1)
+#define POWER_DOWN			BIT(0)
+
+#define QUSB2PHY_REFCLK_ENABLE		BIT(0)
+
+#define PHY_CLK_SCHEME_SEL		BIT(0)
+
+struct qusb2_phy_init_tbl {
+	unsigned int offset;
+	unsigned int val;
+};
+#define QUSB2_PHY_INIT_CFG(o, v) \
+	{			\
+		.offset = o,	\
+		.val = v,	\
+	}
+
+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(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(QUSB2PHY_PLL_AUTOPGM_CTL1, 0x9f),
+	QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_PWR_CTRL, 0x00),
+};
+
+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;
+};
+
+static const struct qusb2_phy_cfg msm8996_phy_cfg = {
+	.tbl = msm8996_init_tbl,
+	.tbl_num = ARRAY_SIZE(msm8996_init_tbl),
+};
+
+/**
+ * struct qusb2_phy - structure holding qusb2 phy attributes
+ *
+ * @phy: generic phy
+ * @base: iomapped memory space for qubs2 phy
+ *
+ * @cfg_ahb_clk: AHB2PHY interface clock
+ * @ref_clk: phy reference clock
+ * @iface_clk: phy interface clock
+ *
+ * @phy_reset: phy reset control
+ *
+ * @vdda_phy: vdd supply to the phy core block
+ * @vdda_pll: 1.8V vdd supply to ref_clk block
+ * @vdda_phy_dpdm: 3.1V vdd supply to Dp/Dm port signals
+ * @tcsr: TCSR syscon register map
+ * @cell: nvmem cell containing phy tuning value
+ *
+ * @cfg: phy config data
+ * @has_se_clk_scheme: indicate if PHY has single-ended ref clock scheme
+ */
+struct qusb2_phy {
+	struct phy *phy;
+	void __iomem *base;
+
+	struct clk *cfg_ahb_clk;
+	struct clk *ref_clk;
+	struct clk *iface_clk;
+
+	struct reset_control *phy_reset;
+
+	struct regulator *vdd_phy;
+	struct regulator *vdda_pll;
+	struct regulator *vdda_phy_dpdm;
+
+	struct regmap *tcsr;
+	struct nvmem_cell *cell;
+
+	const struct qusb2_phy_cfg *cfg;
+	bool has_se_clk_scheme;
+};
+
+static inline void qusb2_setbits(void __iomem *base, u32 offset, u32 val)
+{
+	u32 reg;
+
+	reg = readl_relaxed(base + offset);
+	reg |= val;
+	writel_relaxed(reg, base + offset);
+
+	/* Ensure above write is completed */
+	mb();
+}
+
+static inline void qusb2_clrbits(void __iomem *base, u32 offset, u32 val)
+{
+	u32 reg;
+
+	reg = readl_relaxed(base + offset);
+	reg &= ~val;
+	writel_relaxed(reg, base + offset);
+
+	/* Ensure above write is completed */
+	mb();
+}
+
+static inline void qcom_qusb2_phy_configure(void __iomem *base,
+				const struct qusb2_phy_init_tbl tbl[],
+				int num)
+{
+	int i;
+
+	for (i = 0; i < num; i++)
+		writel_relaxed(tbl[i].val, base + tbl[i].offset);
+
+	/* flush buffered writes */
+	mb();
+}
+
+static int qusb2_phy_toggle_power(struct qusb2_phy *qphy, bool on)
+{
+	struct device *dev = &qphy->phy->dev;
+	int ret;
+
+	if (!on) {
+		ret = 0;
+		goto disable_vdda_phy_dpdm;
+	}
+
+	ret = regulator_enable(qphy->vdd_phy);
+	if (ret) {
+		dev_err(dev, "failed to enable vdd-phy, %d\n", ret);
+		goto err_vdd_phy;
+	}
+
+	ret = regulator_enable(qphy->vdda_pll);
+	if (ret) {
+		dev_err(dev, "failed to enable vdda-pll, %d\n", ret);
+		goto disable_vdd_phy;
+	}
+
+	ret = regulator_enable(qphy->vdda_phy_dpdm);
+	if (ret) {
+		dev_err(dev, "failed to enable vdda-phy-dpdm, %d\n", ret);
+		goto disable_vdda_pll;
+	}
+
+	dev_vdbg(dev, "%s(): regulators are turned on\n", __func__);
+
+	return ret;
+
+disable_vdda_phy_dpdm:
+	regulator_disable(qphy->vdda_phy_dpdm);
+disable_vdda_pll:
+	regulator_disable(qphy->vdda_pll);
+disable_vdd_phy:
+	regulator_disable(qphy->vdd_phy);
+err_vdd_phy:
+	dev_vdbg(dev, "%s(): regulators are turned off\n", __func__);
+	return ret;
+}
+
+/*
+ * Fetches HS Tx tuning value from nvmem and sets the
+ * QUSB2PHY_PORT_TUNE2 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;
+	u8 *val;
+
+	/*
+	 * Read efuse register having TUNE2 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
+	 * configuring phy.
+	 */
+	val = nvmem_cell_read(qphy->cell, NULL);
+	if (IS_ERR(val) || !val[0]) {
+		dev_dbg(dev, "failed to read a valid hs-tx trim value\n");
+		return;
+	}
+
+	/* Fused TUNE2 value is the higher nibble only */
+	qusb2_setbits(qphy->base, QUSB2PHY_PORT_TUNE2, val[0] << 0x4);
+}
+
+static int qusb2_phy_poweron(struct phy *phy)
+{
+	struct qusb2_phy *qphy = phy_get_drvdata(phy);
+	int ret;
+
+	dev_vdbg(&phy->dev, "%s(): Powering-on QUSB2 phy\n", __func__);
+
+	/* turn on regulator supplies */
+	ret = qusb2_phy_toggle_power(qphy, true);
+	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);
+
+	return ret;
+}
+
+static int qusb2_phy_poweroff(struct phy *phy)
+{
+	struct qusb2_phy *qphy = phy_get_drvdata(phy);
+
+	clk_disable_unprepare(qphy->iface_clk);
+
+	qusb2_phy_toggle_power(qphy, false);
+
+	return 0;
+}
+
+static int qusb2_phy_init(struct phy *phy)
+{
+	struct qusb2_phy *qphy = phy_get_drvdata(phy);
+	unsigned int val;
+	unsigned int clk_scheme;
+	int ret;
+
+	dev_vdbg(&phy->dev, "%s(): Initializing QUSB2 phy\n", __func__);
+
+	/* 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;
+	}
+
+	/* Perform phy reset */
+	ret = reset_control_assert(qphy->phy_reset);
+	if (ret) {
+		dev_err(&phy->dev, "failed to assert phy_reset, %d\n", ret);
+		goto disable_ahb_clk;
+	}
+
+	/* 100 us delay to keep PHY in reset mode */
+	usleep_range(100, 150);
+
+	ret = reset_control_deassert(qphy->phy_reset);
+	if (ret) {
+		dev_err(&phy->dev, "failed to de-assert phy_reset, %d\n", ret);
+		goto disable_ahb_clk;
+	}
+
+	/* Disable the PHY */
+	qusb2_setbits(qphy->base, QUSB2PHY_PORT_POWERDOWN,
+			CLAMP_N_EN | FREEZIO_N | POWER_DOWN);
+
+	/* save reset value to override reference clock scheme later */
+	val = readl_relaxed(qphy->base + QUSB2PHY_PLL_TEST);
+
+	qcom_qusb2_phy_configure(qphy->base, qphy->cfg->tbl,
+					qphy->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);
+
+	/* Required to get phy pll lock successfully */
+	usleep_range(150, 160);
+
+	/* Default is single-ended clock on msm8996 */
+	qphy->has_se_clk_scheme = true;
+	/*
+	 * read TCSR_PHY_CLK_SCHEME register to check if single-ended
+	 * clock scheme is selected. If yes, then disable differential
+	 * ref_clk and use single-ended clock, otherwise use differential
+	 * ref_clk only.
+	 */
+	if (qphy->tcsr) {
+		ret = regmap_read(qphy->tcsr, qphy->cfg->clk_scheme_offset,
+							&clk_scheme);
+		if (ret) {
+			dev_err(&phy->dev, "failed to read clk scheme reg\n");
+			goto assert_phy_reset;
+		}
+
+		/* is it a differential clock scheme ? */
+		if (!(clk_scheme & PHY_CLK_SCHEME_SEL)) {
+			dev_vdbg(&phy->dev, "%s(): select differential clk\n",
+								__func__);
+			qphy->has_se_clk_scheme = false;
+		} else {
+			dev_vdbg(&phy->dev, "%s(): select single-ended clk\n",
+								__func__);
+		}
+	}
+
+	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_relaxed(val, qphy->base + QUSB2PHY_PLL_TEST);
+
+	/* Make sure that above write is completed to get PLL source clock */
+	wmb();
+
+	/* Required to get phy pll lock successfully */
+	usleep_range(100, 110);
+
+	val = readb_relaxed(qphy->base + QUSB2PHY_PLL_STATUS);
+	if (!(val & PLL_LOCKED)) {
+		dev_err(&phy->dev,
+			"QUSB2PHY pll lock failed: status reg = %x\n", val);
+		ret = -EBUSY;
+		goto disable_ref_clk;
+	}
+
+	return 0;
+
+disable_ref_clk:
+	if (!qphy->has_se_clk_scheme)
+		clk_disable_unprepare(qphy->ref_clk);
+assert_phy_reset:
+	reset_control_assert(qphy->phy_reset);
+disable_ahb_clk:
+	clk_disable_unprepare(qphy->cfg_ahb_clk);
+	return ret;
+}
+
+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);
+
+	if (!qphy->has_se_clk_scheme)
+		clk_disable_unprepare(qphy->ref_clk);
+
+	reset_control_assert(qphy->phy_reset);
+
+	clk_disable_unprepare(qphy->cfg_ahb_clk);
+
+	return 0;
+}
+
+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,
+	.owner		= THIS_MODULE,
+};
+
+static const struct of_device_id qusb2_phy_of_match_table[] = {
+	{
+		.compatible	= "qcom,msm8996-qusb2-phy",
+		.data		= &msm8996_phy_cfg,
+	},
+	{ },
+};
+MODULE_DEVICE_TABLE(of, qusb2_phy_of_match_table);
+
+static int qusb2_phy_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct qusb2_phy *qphy;
+	struct phy_provider *phy_provider;
+	struct phy *generic_phy;
+	struct resource *res;
+	int ret;
+
+	qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL);
+	if (!qphy)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	qphy->base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(qphy->base))
+		return PTR_ERR(qphy->base);
+
+	qphy->cfg_ahb_clk = devm_clk_get(dev, "cfg_ahb");
+	if (IS_ERR(qphy->cfg_ahb_clk)) {
+		ret = PTR_ERR(qphy->cfg_ahb_clk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get cfg ahb clk, %d\n", ret);
+		return ret;
+	}
+
+	qphy->ref_clk = devm_clk_get(dev, "ref");
+	if (IS_ERR(qphy->ref_clk)) {
+		ret = PTR_ERR(qphy->ref_clk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get ref clk, %d\n", ret);
+		return ret;
+	}
+
+	qphy->iface_clk = devm_clk_get(dev, "iface");
+	if (IS_ERR(qphy->iface_clk)) {
+		ret = PTR_ERR(qphy->iface_clk);
+		if (ret == -EPROBE_DEFER)
+			return ret;
+		qphy->iface_clk = NULL;
+		dev_dbg(dev, "failed to get iface clk, %d\n", ret);
+	}
+
+	qphy->phy_reset = devm_reset_control_get_by_index(&pdev->dev, 0);
+	if (IS_ERR(qphy->phy_reset)) {
+		dev_err(dev, "failed to get phy core reset\n");
+		return PTR_ERR(qphy->phy_reset);
+	}
+
+	qphy->vdd_phy = devm_regulator_get(dev, "vdd-phy");
+	if (IS_ERR(qphy->vdd_phy))
+		return PTR_ERR(qphy->vdd_phy);
+
+	qphy->vdda_pll = devm_regulator_get(dev, "vdda-pll");
+	if (IS_ERR(qphy->vdda_pll))
+		return PTR_ERR(qphy->vdda_pll);
+
+	qphy->vdda_phy_dpdm = devm_regulator_get(dev, "vdda-phy-dpdm");
+	if (IS_ERR(qphy->vdda_phy_dpdm))
+		return PTR_ERR(qphy->vdda_phy_dpdm);
+
+	/* Get the specific init parameters of QMP phy */
+	qphy->cfg = of_device_get_match_data(dev);
+
+	qphy->tcsr = syscon_regmap_lookup_by_phandle(dev->of_node,
+							"qcom,tcsr-syscon");
+	if (IS_ERR(qphy->tcsr)) {
+		dev_dbg(dev, "failed to lookup TCSR regmap\n");
+		qphy->tcsr = NULL;
+	}
+
+	qphy->cell = devm_nvmem_cell_get(dev, NULL);
+	if (IS_ERR(qphy->cell)) {
+		if (PTR_ERR(qphy->cell) == -EPROBE_DEFER)
+			return -EPROBE_DEFER;
+		qphy->cell = NULL;
+		dev_dbg(dev, "failed to lookup tune2 hstx trim value\n");
+	}
+
+	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);
+		return ret;
+	}
+	qphy->phy = generic_phy;
+
+	dev_set_drvdata(dev, qphy);
+	phy_set_drvdata(generic_phy, qphy);
+
+	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+	if (IS_ERR(phy_provider)) {
+		ret = PTR_ERR(phy_provider);
+		dev_err(dev, "failed to register phy, %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct platform_driver qusb2_phy_driver = {
+	.probe		= qusb2_phy_probe,
+	.driver = {
+		.name	= "qcom-qusb2-phy",
+		.of_match_table = qusb2_phy_of_match_table,
+	},
+};
+
+module_platform_driver(qusb2_phy_driver);
+
+MODULE_AUTHOR("Vivek Gautam <vivek.gautam@codeaurora.org>");
+MODULE_DESCRIPTION("Qualcomm QUSB2 PHY driver");
+MODULE_LICENSE("GPL v2");
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project

[toc] | [next] | [standalone]


#1595939 — Re: [PATCH v5 2/4] phy: qcom-qusb2: New driver for QUSB2 PHY on Qcom chips

FromBjorn Andersson <bjorn.andersson@linaro.org>
Date2017-03-09 12:20 +0100
SubjectRe: [PATCH v5 2/4] phy: qcom-qusb2: New driver for QUSB2 PHY on Qcom chips
Message-ID<tj4gW-4pf-5@gated-at.bofh.it>
In reply to#1595814
On Thu 09 Mar 10:07 CET 2017, Vivek Gautam wrote:

[..]
> +static inline void qusb2_setbits(void __iomem *base, u32 offset, u32 val)
> +{
> +	u32 reg;
> +
> +	reg = readl_relaxed(base + offset);
> +	reg |= val;
> +	writel_relaxed(reg, base + offset);
> +
> +	/* Ensure above write is completed */
> +	mb();

mb() does not ensure that the operation is completed, it only ensures
ordering between this write and subsequent memory accesses.

Unless you have specific reasons to you should not use the _relaxed()
versions of read and write operations. Using the non-relaxed versions
will ensure that your memory operations are not reordered.

> +}
> +
> +static inline void qusb2_clrbits(void __iomem *base, u32 offset, u32 val)
> +{
> +	u32 reg;
> +
> +	reg = readl_relaxed(base + offset);
> +	reg &= ~val;
> +	writel_relaxed(reg, base + offset);
> +
> +	/* Ensure above write is completed */
> +	mb();

Dito.

> +}
> +
> +static inline void qcom_qusb2_phy_configure(void __iomem *base,
> +				const struct qusb2_phy_init_tbl tbl[],
> +				int num)
> +{
> +	int i;
> +
> +	for (i = 0; i < num; i++)
> +		writel_relaxed(tbl[i].val, base + tbl[i].offset);
> +
> +	/* flush buffered writes */
> +	mb();

Dito.

> +}
> +
> +static int qusb2_phy_toggle_power(struct qusb2_phy *qphy, bool on)
> +{
> +	struct device *dev = &qphy->phy->dev;
> +	int ret;
> +
> +	if (!on) {
> +		ret = 0;
> +		goto disable_vdda_phy_dpdm;
> +	}

This is an ugly hack.

> +
> +	ret = regulator_enable(qphy->vdd_phy);
> +	if (ret) {
> +		dev_err(dev, "failed to enable vdd-phy, %d\n", ret);
> +		goto err_vdd_phy;
> +	}
> +
> +	ret = regulator_enable(qphy->vdda_pll);
> +	if (ret) {
> +		dev_err(dev, "failed to enable vdda-pll, %d\n", ret);
> +		goto disable_vdd_phy;
> +	}
> +
> +	ret = regulator_enable(qphy->vdda_phy_dpdm);
> +	if (ret) {
> +		dev_err(dev, "failed to enable vdda-phy-dpdm, %d\n", ret);
> +		goto disable_vdda_pll;
> +	}
> +
> +	dev_vdbg(dev, "%s(): regulators are turned on\n", __func__);
> +
> +	return ret;
> +
> +disable_vdda_phy_dpdm:
> +	regulator_disable(qphy->vdda_phy_dpdm);
> +disable_vdda_pll:
> +	regulator_disable(qphy->vdda_pll);
> +disable_vdd_phy:
> +	regulator_disable(qphy->vdd_phy);
> +err_vdd_phy:
> +	dev_vdbg(dev, "%s(): regulators are turned off\n", __func__);
> +	return ret;
> +}

If you use the regulator_bulk interface for your regulators you can
replace this entire function with a call to regulator_bulk_enable() and
regulator_bulk_disable().

> +
> +/*
> + * Fetches HS Tx tuning value from nvmem and sets the
> + * QUSB2PHY_PORT_TUNE2 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;
> +	u8 *val;
> +
> +	/*
> +	 * Read efuse register having TUNE2 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
> +	 * configuring phy.
> +	 */
> +	val = nvmem_cell_read(qphy->cell, NULL);
> +	if (IS_ERR(val) || !val[0]) {
> +		dev_dbg(dev, "failed to read a valid hs-tx trim value\n");
> +		return;
> +	}
> +
> +	/* Fused TUNE2 value is the higher nibble only */
> +	qusb2_setbits(qphy->base, QUSB2PHY_PORT_TUNE2, val[0] << 0x4);
> +}
> +
> +static int qusb2_phy_poweron(struct phy *phy)
> +{
> +	struct qusb2_phy *qphy = phy_get_drvdata(phy);
> +	int ret;
> +
> +	dev_vdbg(&phy->dev, "%s(): Powering-on QUSB2 phy\n", __func__);
> +
> +	/* turn on regulator supplies */
> +	ret = qusb2_phy_toggle_power(qphy, true);

Call regulator_bulk_enable()

> +	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);
> +
> +	return ret;
> +}
> +
> +static int qusb2_phy_poweroff(struct phy *phy)
> +{
> +	struct qusb2_phy *qphy = phy_get_drvdata(phy);
> +
> +	clk_disable_unprepare(qphy->iface_clk);
> +
> +	qusb2_phy_toggle_power(qphy, false);

Call regulator_bulk_disable()

> +
> +	return 0;
> +}
> +
> +static int qusb2_phy_init(struct phy *phy)
> +{
> +	struct qusb2_phy *qphy = phy_get_drvdata(phy);
> +	unsigned int val;
> +	unsigned int clk_scheme;
> +	int ret;
> +
> +	dev_vdbg(&phy->dev, "%s(): Initializing QUSB2 phy\n", __func__);
> +
> +	/* 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;
> +	}
> +
> +	/* Perform phy reset */
> +	ret = reset_control_assert(qphy->phy_reset);
> +	if (ret) {
> +		dev_err(&phy->dev, "failed to assert phy_reset, %d\n", ret);
> +		goto disable_ahb_clk;
> +	}
> +
> +	/* 100 us delay to keep PHY in reset mode */
> +	usleep_range(100, 150);
> +
> +	ret = reset_control_deassert(qphy->phy_reset);
> +	if (ret) {
> +		dev_err(&phy->dev, "failed to de-assert phy_reset, %d\n", ret);
> +		goto disable_ahb_clk;
> +	}
> +
> +	/* Disable the PHY */
> +	qusb2_setbits(qphy->base, QUSB2PHY_PORT_POWERDOWN,
> +			CLAMP_N_EN | FREEZIO_N | POWER_DOWN);
> +
> +	/* save reset value to override reference clock scheme later */
> +	val = readl_relaxed(qphy->base + QUSB2PHY_PLL_TEST);
> +
> +	qcom_qusb2_phy_configure(qphy->base, qphy->cfg->tbl,
> +					qphy->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);
> +

The mb() in clrbits will not ensure that the write is finished before
the sleep, it will only ensure that above write is done before any later
memory operations. If you want to ensure that the value has hit the
hardware before the sleep you should simply read back the value from the
register.

> +	/* Required to get phy pll lock successfully */
> +	usleep_range(150, 160);
> +
> +	/* Default is single-ended clock on msm8996 */
> +	qphy->has_se_clk_scheme = true;
> +	/*
> +	 * read TCSR_PHY_CLK_SCHEME register to check if single-ended
> +	 * clock scheme is selected. If yes, then disable differential
> +	 * ref_clk and use single-ended clock, otherwise use differential
> +	 * ref_clk only.
> +	 */
> +	if (qphy->tcsr) {
> +		ret = regmap_read(qphy->tcsr, qphy->cfg->clk_scheme_offset,
> +							&clk_scheme);
> +		if (ret) {
> +			dev_err(&phy->dev, "failed to read clk scheme reg\n");
> +			goto assert_phy_reset;
> +		}
> +
> +		/* is it a differential clock scheme ? */
> +		if (!(clk_scheme & PHY_CLK_SCHEME_SEL)) {
> +			dev_vdbg(&phy->dev, "%s(): select differential clk\n",
> +								__func__);
> +			qphy->has_se_clk_scheme = false;
> +		} else {
> +			dev_vdbg(&phy->dev, "%s(): select single-ended clk\n",
> +								__func__);
> +		}
> +	}
> +
> +	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_relaxed(val, qphy->base + QUSB2PHY_PLL_TEST);
> +
> +	/* Make sure that above write is completed to get PLL source clock */
> +	wmb();
> +

As above.

> +	/* Required to get phy pll lock successfully */
> +	usleep_range(100, 110);
> +

Regards,
Bjorn

[toc] | [prev] | [next] | [standalone]


#1596712 — Re: [PATCH v5 2/4] phy: qcom-qusb2: New driver for QUSB2 PHY on Qcom chips

FromVivek Gautam <vivek.gautam@codeaurora.org>
Date2017-03-10 09:50 +0100
SubjectRe: [PATCH v5 2/4] phy: qcom-qusb2: New driver for QUSB2 PHY on Qcom chips
Message-ID<tjopj-1j2-11@gated-at.bofh.it>
In reply to#1595939
Hi,


On 03/09/2017 04:31 PM, Bjorn Andersson wrote:
> On Thu 09 Mar 10:07 CET 2017, Vivek Gautam wrote:
>
> [..]
>> +static inline void qusb2_setbits(void __iomem *base, u32 offset, u32 val)
>> +{
>> +	u32 reg;
>> +
>> +	reg = readl_relaxed(base + offset);
>> +	reg |= val;
>> +	writel_relaxed(reg, base + offset);
>> +
>> +	/* Ensure above write is completed */
>> +	mb();
> mb() does not ensure that the operation is completed, it only ensures
> ordering between this write and subsequent memory accesses.
>
> Unless you have specific reasons to you should not use the _relaxed()
> versions of read and write operations. Using the non-relaxed versions
> will ensure that your memory operations are not reordered.

Right, I think this is something i took from downstream and didn't
change. I should rather be using readl()/writel() and then readl()
again to ensure writes are through.
Thanks for pointing out.

>
>> +}
>> +
>> +static inline void qusb2_clrbits(void __iomem *base, u32 offset, u32 val)
>> +{
>> +	u32 reg;
>> +
>> +	reg = readl_relaxed(base + offset);
>> +	reg &= ~val;
>> +	writel_relaxed(reg, base + offset);
>> +
>> +	/* Ensure above write is completed */
>> +	mb();
> Dito.

Sure, will change it.

>
>> +}
>> +
>> +static inline void qcom_qusb2_phy_configure(void __iomem *base,
>> +				const struct qusb2_phy_init_tbl tbl[],
>> +				int num)
>> +{
>> +	int i;
>> +
>> +	for (i = 0; i < num; i++)
>> +		writel_relaxed(tbl[i].val, base + tbl[i].offset);
>> +
>> +	/* flush buffered writes */
>> +	mb();
> Dito.

will correct it.

>
>> +}
>> +
>> +static int qusb2_phy_toggle_power(struct qusb2_phy *qphy, bool on)
>> +{
>> +	struct device *dev = &qphy->phy->dev;
>> +	int ret;
>> +
>> +	if (!on) {
>> +		ret = 0;
>> +		goto disable_vdda_phy_dpdm;
>> +	}
> This is an ugly hack.

Right, it sort of minimized the code. :)
It just jumps to disable code for an 'off' call.

But, will amend it to use regulator_bulk_*() apis.

>
>> +
>> +	ret = regulator_enable(qphy->vdd_phy);
>> +	if (ret) {
>> +		dev_err(dev, "failed to enable vdd-phy, %d\n", ret);
>> +		goto err_vdd_phy;
>> +	}
>> +
>> +	ret = regulator_enable(qphy->vdda_pll);
>> +	if (ret) {
>> +		dev_err(dev, "failed to enable vdda-pll, %d\n", ret);
>> +		goto disable_vdd_phy;
>> +	}
>> +
>> +	ret = regulator_enable(qphy->vdda_phy_dpdm);
>> +	if (ret) {
>> +		dev_err(dev, "failed to enable vdda-phy-dpdm, %d\n", ret);
>> +		goto disable_vdda_pll;
>> +	}
>> +
>> +	dev_vdbg(dev, "%s(): regulators are turned on\n", __func__);
>> +
>> +	return ret;
>> +
>> +disable_vdda_phy_dpdm:
>> +	regulator_disable(qphy->vdda_phy_dpdm);
>> +disable_vdda_pll:
>> +	regulator_disable(qphy->vdda_pll);
>> +disable_vdd_phy:
>> +	regulator_disable(qphy->vdd_phy);
>> +err_vdd_phy:
>> +	dev_vdbg(dev, "%s(): regulators are turned off\n", __func__);
>> +	return ret;
>> +}
> If you use the regulator_bulk interface for your regulators you can
> replace this entire function with a call to regulator_bulk_enable() and
> regulator_bulk_disable().

Sure, will change this as suggested.

>
>> +
>> +/*
>> + * Fetches HS Tx tuning value from nvmem and sets the
>> + * QUSB2PHY_PORT_TUNE2 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;
>> +	u8 *val;
>> +
>> +	/*
>> +	 * Read efuse register having TUNE2 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
>> +	 * configuring phy.
>> +	 */
>> +	val = nvmem_cell_read(qphy->cell, NULL);
>> +	if (IS_ERR(val) || !val[0]) {
>> +		dev_dbg(dev, "failed to read a valid hs-tx trim value\n");
>> +		return;
>> +	}
>> +
>> +	/* Fused TUNE2 value is the higher nibble only */
>> +	qusb2_setbits(qphy->base, QUSB2PHY_PORT_TUNE2, val[0] << 0x4);
>> +}
>> +
>> +static int qusb2_phy_poweron(struct phy *phy)
>> +{
>> +	struct qusb2_phy *qphy = phy_get_drvdata(phy);
>> +	int ret;
>> +
>> +	dev_vdbg(&phy->dev, "%s(): Powering-on QUSB2 phy\n", __func__);
>> +
>> +	/* turn on regulator supplies */
>> +	ret = qusb2_phy_toggle_power(qphy, true);
> Call regulator_bulk_enable()

Sure.

>
>> +	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);
>> +
>> +	return ret;
>> +}
>> +
>> +static int qusb2_phy_poweroff(struct phy *phy)
>> +{
>> +	struct qusb2_phy *qphy = phy_get_drvdata(phy);
>> +
>> +	clk_disable_unprepare(qphy->iface_clk);
>> +
>> +	qusb2_phy_toggle_power(qphy, false);
> Call regulator_bulk_disable()

Right, will do that.

>
>> +
>> +	return 0;
>> +}
>> +
>> +static int qusb2_phy_init(struct phy *phy)
>> +{
>> +	struct qusb2_phy *qphy = phy_get_drvdata(phy);
>> +	unsigned int val;
>> +	unsigned int clk_scheme;
>> +	int ret;
>> +
>> +	dev_vdbg(&phy->dev, "%s(): Initializing QUSB2 phy\n", __func__);
>> +
>> +	/* 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;
>> +	}
>> +
>> +	/* Perform phy reset */
>> +	ret = reset_control_assert(qphy->phy_reset);
>> +	if (ret) {
>> +		dev_err(&phy->dev, "failed to assert phy_reset, %d\n", ret);
>> +		goto disable_ahb_clk;
>> +	}
>> +
>> +	/* 100 us delay to keep PHY in reset mode */
>> +	usleep_range(100, 150);
>> +
>> +	ret = reset_control_deassert(qphy->phy_reset);
>> +	if (ret) {
>> +		dev_err(&phy->dev, "failed to de-assert phy_reset, %d\n", ret);
>> +		goto disable_ahb_clk;
>> +	}
>> +
>> +	/* Disable the PHY */
>> +	qusb2_setbits(qphy->base, QUSB2PHY_PORT_POWERDOWN,
>> +			CLAMP_N_EN | FREEZIO_N | POWER_DOWN);
>> +
>> +	/* save reset value to override reference clock scheme later */
>> +	val = readl_relaxed(qphy->base + QUSB2PHY_PLL_TEST);
>> +
>> +	qcom_qusb2_phy_configure(qphy->base, qphy->cfg->tbl,
>> +					qphy->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);
>> +
> The mb() in clrbits will not ensure that the write is finished before
> the sleep, it will only ensure that above write is done before any later
> memory operations. If you want to ensure that the value has hit the
> hardware before the sleep you should simply read back the value from the
> register.

Right. Will add a read back in qusb2_clrbits() and qusb2_setbits().

>
>> +	/* Required to get phy pll lock successfully */
>> +	usleep_range(150, 160);
>> +
>> +	/* Default is single-ended clock on msm8996 */
>> +	qphy->has_se_clk_scheme = true;
>> +	/*
>> +	 * read TCSR_PHY_CLK_SCHEME register to check if single-ended
>> +	 * clock scheme is selected. If yes, then disable differential
>> +	 * ref_clk and use single-ended clock, otherwise use differential
>> +	 * ref_clk only.
>> +	 */
>> +	if (qphy->tcsr) {
>> +		ret = regmap_read(qphy->tcsr, qphy->cfg->clk_scheme_offset,
>> +							&clk_scheme);
>> +		if (ret) {
>> +			dev_err(&phy->dev, "failed to read clk scheme reg\n");
>> +			goto assert_phy_reset;
>> +		}
>> +
>> +		/* is it a differential clock scheme ? */
>> +		if (!(clk_scheme & PHY_CLK_SCHEME_SEL)) {
>> +			dev_vdbg(&phy->dev, "%s(): select differential clk\n",
>> +								__func__);
>> +			qphy->has_se_clk_scheme = false;
>> +		} else {
>> +			dev_vdbg(&phy->dev, "%s(): select single-ended clk\n",
>> +								__func__);
>> +		}
>> +	}
>> +
>> +	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_relaxed(val, qphy->base + QUSB2PHY_PLL_TEST);
>> +
>> +	/* Make sure that above write is completed to get PLL source clock */
>> +	wmb();
>> +
> As above.

Sure, will change this.

>> +	/* Required to get phy pll lock successfully */
>> +	usleep_range(100, 110);
>> +
> Regards,
> Bjorn


Regards
Vivek

-- 
The Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project

[toc] | [prev] | [standalone]


Back to top | Article view | linux.kernel


csiph-web