All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v7 0/5] phy: qcom-ufs: add support for QUALCOMM
@ 2015-01-15 14:32 Yaniv Gardi
  2015-01-15 14:32   ` Yaniv Gardi
                   ` (5 more replies)
  0 siblings, 6 replies; 27+ messages in thread
From: Yaniv Gardi @ 2015-01-15 14:32 UTC (permalink / raw)
  To: James.Bottomley, hch
  Cc: linux-kernel, linux-scsi, linux-arm-msm, santoshsy,
	linux-scsi-owner, subhashj, ygardi, noag, draviv

Changes from V6:
- change#1 in the V6 series: 0001-phy-qcom-ufs-add-support-for-20nm-phy
  has been divided into 2 patches:
  A: phy: qcom-ufs: add support for QUALCOMM Technologies UFS PHY drivers
  B: phy: qcom-ufs: add support for 20nm phy
  in order to seperate the common code of the Qualcomm Technologies
  UFS PHYs and the specific UFS PHYs that will be added.

Yaniv Gardi (5):
  phy: qcom-ufs: add support for QUALCOMM Technologies UFS PHY drivers
  phy: qcom-ufs: add support for 20nm phy
  scsi: ufs-qcom: add support for Qualcomm Technologies Inc platforms
  phy: qcom-ufs: add support for 14nm phy
  scsi: ufs-qcom-ice: add Inline Crypto Engine (ICE) support for UFS

 drivers/phy/Kconfig                 |    7 +
 drivers/phy/Makefile                |    3 +
 drivers/phy/phy-qcom-ufs-i.h        |  159 ++++++
 drivers/phy/phy-qcom-ufs-qmp-14nm.c |  201 +++++++
 drivers/phy/phy-qcom-ufs-qmp-14nm.h |  177 ++++++
 drivers/phy/phy-qcom-ufs-qmp-20nm.c |  257 +++++++++
 drivers/phy/phy-qcom-ufs-qmp-20nm.h |  235 ++++++++
 drivers/phy/phy-qcom-ufs.c          |  745 ++++++++++++++++++++++++
 drivers/scsi/ufs/Kconfig            |   25 +
 drivers/scsi/ufs/Makefile           |    2 +
 drivers/scsi/ufs/ufs-qcom-ice.c     |  520 +++++++++++++++++
 drivers/scsi/ufs/ufs-qcom-ice.h     |  113 ++++
 drivers/scsi/ufs/ufs-qcom.c         | 1058 +++++++++++++++++++++++++++++++++++
 drivers/scsi/ufs/ufs-qcom.h         |  195 +++++++
 include/linux/phy/phy-qcom-ufs.h    |   59 ++
 15 files changed, 3756 insertions(+)
 create mode 100644 drivers/phy/phy-qcom-ufs-i.h
 create mode 100644 drivers/phy/phy-qcom-ufs-qmp-14nm.c
 create mode 100644 drivers/phy/phy-qcom-ufs-qmp-14nm.h
 create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.c
 create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.h
 create mode 100644 drivers/phy/phy-qcom-ufs.c
 create mode 100644 drivers/scsi/ufs/ufs-qcom-ice.c
 create mode 100644 drivers/scsi/ufs/ufs-qcom-ice.h
 create mode 100644 drivers/scsi/ufs/ufs-qcom.c
 create mode 100644 drivers/scsi/ufs/ufs-qcom.h
 create mode 100644 include/linux/phy/phy-qcom-ufs.h

-- 
1.8.5.2

-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation

^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v7 1/5] phy: qcom-ufs: add support for QUALCOMM Technologies UFS PHY drivers
  2015-01-15 14:32 [PATCH v7 0/5] phy: qcom-ufs: add support for QUALCOMM Yaniv Gardi
@ 2015-01-15 14:32   ` Yaniv Gardi
  2015-01-15 14:32   ` Yaniv Gardi
                     ` (4 subsequent siblings)
  5 siblings, 0 replies; 27+ messages in thread
From: Yaniv Gardi @ 2015-01-15 14:32 UTC (permalink / raw)
  To: James.Bottomley, hch
  Cc: linux-kernel, linux-scsi, linux-arm-msm, santoshsy,
	linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Kishon Vijay Abraham I, Grant Likely, Rob Herring,
	open list:OPEN FIRMWARE AND...

This change adds a generic and common API support for ufs phy QUALCOMM
Technologies. This support provides common code and also points
to specific phy callbacks to differentiate between different behaviors
of frequent use-cases (like power on, power off, phy calibration etc).

Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>

---
 drivers/phy/Kconfig          |   7 +
 drivers/phy/Makefile         |   1 +
 drivers/phy/phy-qcom-ufs-i.h | 118 +++++++
 drivers/phy/phy-qcom-ufs.c   | 745 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 871 insertions(+)
 create mode 100644 drivers/phy/phy-qcom-ufs-i.h
 create mode 100644 drivers/phy/phy-qcom-ufs.c

diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index ccad880..26a7623 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -277,4 +277,11 @@ config PHY_STIH41X_USB
 	  Enable this to support the USB transceiver that is part of
 	  STMicroelectronics STiH41x SoC series.
 
+config PHY_QCOM_UFS
+	tristate "Qualcomm UFS PHY driver"
+	depends on OF && ARCH_MSM
+	select GENERIC_PHY
+	help
+	  Support for UFS PHY on QCOM chipsets.
+
 endmenu
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index aa74f96..335965d 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -34,3 +34,4 @@ 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_STIH41X_USB)		+= phy-stih41x-usb.o
+obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs.o
diff --git a/drivers/phy/phy-qcom-ufs-i.h b/drivers/phy/phy-qcom-ufs-i.h
new file mode 100644
index 0000000..dac200f
--- /dev/null
+++ b/drivers/phy/phy-qcom-ufs-i.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2013-2015, 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.
+ *
+ */
+
+#ifndef UFS_QCOM_PHY_I_H_
+#define UFS_QCOM_PHY_I_H_
+
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+
+#define UFS_QCOM_PHY_NAME_LEN	30
+
+struct ufs_qcom_phy_calibration {
+	u32 reg_offset;
+	u32 cfg_value;
+};
+
+struct ufs_qcom_phy_vreg {
+	const char *name;
+	struct regulator *reg;
+	int max_uA;
+	int min_uV;
+	int max_uV;
+	bool enabled;
+	bool is_always_on;
+};
+
+struct ufs_qcom_phy {
+	struct list_head list;
+	struct device *dev;
+	void __iomem *mmio;
+	void __iomem *dev_ref_clk_ctrl_mmio;
+	struct clk *tx_iface_clk;
+	struct clk *rx_iface_clk;
+	bool is_iface_clk_enabled;
+	struct clk *ref_clk_src;
+	struct clk *ref_clk_parent;
+	struct clk *ref_clk;
+	bool is_ref_clk_enabled;
+	bool is_dev_ref_clk_enabled;
+	struct ufs_qcom_phy_vreg vdda_pll;
+	struct ufs_qcom_phy_vreg vdda_phy;
+	struct ufs_qcom_phy_vreg vddp_ref_clk;
+	unsigned int quirks;
+
+	/*
+	* If UFS link is put into Hibern8 and if UFS PHY analog hardware is
+	* power collapsed (by clearing UFS_PHY_POWER_DOWN_CONTROL), Hibern8
+	* exit might fail even after powering on UFS PHY analog hardware.
+	* Enabling this quirk will help to solve above issue by doing
+	* custom PHY settings just before PHY analog power collapse.
+	*/
+	#define UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE	BIT(0)
+
+	u8 host_ctrl_rev_major;
+	u16 host_ctrl_rev_minor;
+	u16 host_ctrl_rev_step;
+
+	char name[UFS_QCOM_PHY_NAME_LEN];
+	struct ufs_qcom_phy_calibration *cached_regs;
+	int cached_regs_table_size;
+	bool is_powered_on;
+	struct ufs_qcom_phy_specific_ops *phy_spec_ops;
+};
+
+/**
+ * struct ufs_qcom_phy_specific_ops - set of pointers to functions which have a
+ * specific implementation per phy. Each UFS phy, should implement
+ * those functions according to its spec and requirements
+ * @calibrate_phy: pointer to a function that calibrate the phy
+ * @start_serdes: pointer to a function that starts the serdes
+ * @is_physical_coding_sublayer_ready: pointer to a function that
+ * checks pcs readiness. returns 0 for success and non-zero for error.
+ * @set_tx_lane_enable: pointer to a function that enable tx lanes
+ * @power_control: pointer to a function that controls analog rail of phy
+ * and writes to QSERDES_RX_SIGDET_CNTRL attribute
+ */
+struct ufs_qcom_phy_specific_ops {
+	int (*calibrate_phy)(struct ufs_qcom_phy *phy, bool is_rate_B);
+	void (*start_serdes)(struct ufs_qcom_phy *phy);
+	int (*is_physical_coding_sublayer_ready)(struct ufs_qcom_phy *phy);
+	void (*set_tx_lane_enable)(struct ufs_qcom_phy *phy, u32 val);
+	void (*power_control)(struct ufs_qcom_phy *phy, bool val);
+};
+
+struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy);
+int ufs_qcom_phy_power_on(struct phy *generic_phy);
+int ufs_qcom_phy_power_off(struct phy *generic_phy);
+int ufs_qcom_phy_exit(struct phy *generic_phy);
+int ufs_qcom_phy_init_clks(struct phy *generic_phy,
+			struct ufs_qcom_phy *phy_common);
+int ufs_qcom_phy_init_vregulators(struct phy *generic_phy,
+			struct ufs_qcom_phy *phy_common);
+int ufs_qcom_phy_remove(struct phy *generic_phy,
+		       struct ufs_qcom_phy *ufs_qcom_phy);
+struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
+			struct ufs_qcom_phy *common_cfg,
+			struct phy_ops *ufs_qcom_phy_gen_ops,
+			struct ufs_qcom_phy_specific_ops *phy_spec_ops);
+int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
+			struct ufs_qcom_phy_calibration *tbl_A, int tbl_size_A,
+			struct ufs_qcom_phy_calibration *tbl_B, int tbl_size_B,
+			bool is_rate_B);
+#endif
diff --git a/drivers/phy/phy-qcom-ufs.c b/drivers/phy/phy-qcom-ufs.c
new file mode 100644
index 0000000..44ee983
--- /dev/null
+++ b/drivers/phy/phy-qcom-ufs.c
@@ -0,0 +1,745 @@
+/*
+ * Copyright (c) 2013-2015, 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 "phy-qcom-ufs-i.h"
+
+#define MAX_PROP_NAME              32
+#define VDDA_PHY_MIN_UV            1000000
+#define VDDA_PHY_MAX_UV            1000000
+#define VDDA_PLL_MIN_UV            1800000
+#define VDDA_PLL_MAX_UV            1800000
+#define VDDP_REF_CLK_MIN_UV        1200000
+#define VDDP_REF_CLK_MAX_UV        1200000
+
+static int __ufs_qcom_phy_init_vreg(struct phy *, struct ufs_qcom_phy_vreg *,
+				    const char *, bool);
+static int ufs_qcom_phy_init_vreg(struct phy *, struct ufs_qcom_phy_vreg *,
+				  const char *);
+static int ufs_qcom_phy_base_init(struct platform_device *pdev,
+				  struct ufs_qcom_phy *phy_common);
+
+int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
+			   struct ufs_qcom_phy_calibration *tbl_A,
+			   int tbl_size_A,
+			   struct ufs_qcom_phy_calibration *tbl_B,
+			   int tbl_size_B, bool is_rate_B)
+{
+	int i;
+	int ret = 0;
+
+	if (!tbl_A) {
+		dev_err(ufs_qcom_phy->dev, "%s: tbl_A is NULL", __func__);
+		ret = EINVAL;
+		goto out;
+	}
+
+	for (i = 0; i < tbl_size_A; i++)
+		writel_relaxed(tbl_A[i].cfg_value,
+			       ufs_qcom_phy->mmio + tbl_A[i].reg_offset);
+
+	/*
+	 * In case we would like to work in rate B, we need
+	 * to override a registers that were configured in rate A table
+	 * with registers of rate B table.
+	 * table.
+	 */
+	if (is_rate_B) {
+		if (!tbl_B) {
+			dev_err(ufs_qcom_phy->dev, "%s: tbl_B is NULL",
+				__func__);
+			ret = EINVAL;
+			goto out;
+		}
+
+		for (i = 0; i < tbl_size_B; i++)
+			writel_relaxed(tbl_B[i].cfg_value,
+				ufs_qcom_phy->mmio + tbl_B[i].reg_offset);
+	}
+
+	/* flush buffered writes */
+	mb();
+
+out:
+	return ret;
+}
+
+struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
+				struct ufs_qcom_phy *common_cfg,
+				struct phy_ops *ufs_qcom_phy_gen_ops,
+				struct ufs_qcom_phy_specific_ops *phy_spec_ops)
+{
+	int err;
+	struct device *dev = &pdev->dev;
+	struct phy *generic_phy = NULL;
+	struct phy_provider *phy_provider;
+
+	err = ufs_qcom_phy_base_init(pdev, common_cfg);
+	if (err) {
+		dev_err(dev, "%s: phy base init failed %d\n", __func__, err);
+		goto out;
+	}
+
+	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+	if (IS_ERR(phy_provider)) {
+		err = PTR_ERR(phy_provider);
+		dev_err(dev, "%s: failed to register phy %d\n", __func__, err);
+		goto out;
+	}
+
+	generic_phy = devm_phy_create(dev, NULL, ufs_qcom_phy_gen_ops);
+	if (IS_ERR(generic_phy)) {
+		err =  PTR_ERR(generic_phy);
+		dev_err(dev, "%s: failed to create phy %d\n", __func__, err);
+		goto out;
+	}
+
+	common_cfg->phy_spec_ops = phy_spec_ops;
+	common_cfg->dev = dev;
+
+out:
+	return generic_phy;
+}
+
+/*
+ * This assumes the embedded phy structure inside generic_phy is of type
+ * struct ufs_qcom_phy. In order to function properly it's crucial
+ * to keep the embedded struct "struct ufs_qcom_phy common_cfg"
+ * as the first inside generic_phy.
+ */
+struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy)
+{
+	return (struct ufs_qcom_phy *)phy_get_drvdata(generic_phy);
+}
+
+static
+int ufs_qcom_phy_base_init(struct platform_device *pdev,
+			   struct ufs_qcom_phy *phy_common)
+{
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	int err = 0;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_mem");
+	if (!res) {
+		dev_err(dev, "%s: phy_mem resource not found\n", __func__);
+		err = -ENOMEM;
+		goto out;
+	}
+
+	phy_common->mmio = devm_ioremap_resource(dev, res);
+	if (IS_ERR((void const *)phy_common->mmio)) {
+		err = PTR_ERR((void const *)phy_common->mmio);
+		phy_common->mmio = NULL;
+		dev_err(dev, "%s: ioremap for phy_mem resource failed %d\n",
+			__func__, err);
+		goto out;
+	}
+
+	/* "dev_ref_clk_ctrl_mem" is optional resource */
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "dev_ref_clk_ctrl_mem");
+	if (!res) {
+		dev_dbg(dev, "%s: dev_ref_clk_ctrl_mem resource not found\n",
+			__func__);
+		goto out;
+	}
+
+	phy_common->dev_ref_clk_ctrl_mmio = devm_ioremap_resource(dev, res);
+	if (IS_ERR((void const *)phy_common->dev_ref_clk_ctrl_mmio)) {
+		err = PTR_ERR((void const *)phy_common->dev_ref_clk_ctrl_mmio);
+		phy_common->dev_ref_clk_ctrl_mmio = NULL;
+		dev_err(dev, "%s: ioremap for dev_ref_clk_ctrl_mem resource failed %d\n",
+			__func__, err);
+	}
+
+out:
+	return err;
+}
+
+static int __ufs_qcom_phy_clk_get(struct phy *phy,
+			 const char *name, struct clk **clk_out, bool err_print)
+{
+	struct clk *clk;
+	int err = 0;
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
+	struct device *dev = ufs_qcom_phy->dev;
+
+	clk = devm_clk_get(dev, name);
+	if (IS_ERR(clk)) {
+		err = PTR_ERR(clk);
+		if (err_print)
+			dev_err(dev, "failed to get %s err %d", name, err);
+	} else {
+		*clk_out = clk;
+	}
+
+	return err;
+}
+
+static
+int ufs_qcom_phy_clk_get(struct phy *phy,
+			 const char *name, struct clk **clk_out)
+{
+	return __ufs_qcom_phy_clk_get(phy, name, clk_out, true);
+}
+
+int
+ufs_qcom_phy_init_clks(struct phy *generic_phy,
+		       struct ufs_qcom_phy *phy_common)
+{
+	int err;
+
+	err = ufs_qcom_phy_clk_get(generic_phy, "tx_iface_clk",
+				   &phy_common->tx_iface_clk);
+	if (err)
+		goto out;
+
+	err = ufs_qcom_phy_clk_get(generic_phy, "rx_iface_clk",
+				   &phy_common->rx_iface_clk);
+	if (err)
+		goto out;
+
+	err = ufs_qcom_phy_clk_get(generic_phy, "ref_clk_src",
+				   &phy_common->ref_clk_src);
+	if (err)
+		goto out;
+
+	/*
+	 * "ref_clk_parent" is optional hence don't abort init if it's not
+	 * found.
+	 */
+	__ufs_qcom_phy_clk_get(generic_phy, "ref_clk_parent",
+				   &phy_common->ref_clk_parent, false);
+
+	err = ufs_qcom_phy_clk_get(generic_phy, "ref_clk",
+				   &phy_common->ref_clk);
+
+out:
+	return err;
+}
+
+int
+ufs_qcom_phy_init_vregulators(struct phy *generic_phy,
+			      struct ufs_qcom_phy *phy_common)
+{
+	int err;
+
+	err = ufs_qcom_phy_init_vreg(generic_phy, &phy_common->vdda_pll,
+		"vdda-pll");
+	if (err)
+		goto out;
+
+	err = ufs_qcom_phy_init_vreg(generic_phy, &phy_common->vdda_phy,
+		"vdda-phy");
+
+	if (err)
+		goto out;
+
+	/* vddp-ref-clk-* properties are optional */
+	__ufs_qcom_phy_init_vreg(generic_phy, &phy_common->vddp_ref_clk,
+				 "vddp-ref-clk", true);
+out:
+	return err;
+}
+
+static int __ufs_qcom_phy_init_vreg(struct phy *phy,
+		struct ufs_qcom_phy_vreg *vreg, const char *name, bool optional)
+{
+	int err = 0;
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
+	struct device *dev = ufs_qcom_phy->dev;
+
+	char prop_name[MAX_PROP_NAME];
+
+	vreg->name = kstrdup(name, GFP_KERNEL);
+	if (!vreg->name) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	vreg->reg = devm_regulator_get(dev, name);
+	if (IS_ERR(vreg->reg)) {
+		err = PTR_ERR(vreg->reg);
+		vreg->reg = NULL;
+		if (!optional)
+			dev_err(dev, "failed to get %s, %d\n", name, err);
+		goto out;
+	}
+
+	if (dev->of_node) {
+		snprintf(prop_name, MAX_PROP_NAME, "%s-max-microamp", name);
+		err = of_property_read_u32(dev->of_node,
+					prop_name, &vreg->max_uA);
+		if (err && err != -EINVAL) {
+			dev_err(dev, "%s: failed to read %s\n",
+					__func__, prop_name);
+			goto out;
+		} else if (err == -EINVAL || !vreg->max_uA) {
+			if (regulator_count_voltages(vreg->reg) > 0) {
+				dev_err(dev, "%s: %s is mandatory\n",
+						__func__, prop_name);
+				goto out;
+			}
+			err = 0;
+		}
+		snprintf(prop_name, MAX_PROP_NAME, "%s-always-on", name);
+		if (of_get_property(dev->of_node, prop_name, NULL))
+			vreg->is_always_on = true;
+		else
+			vreg->is_always_on = false;
+	}
+
+	if (!strcmp(name, "vdda-pll")) {
+		vreg->max_uV = VDDA_PLL_MAX_UV;
+		vreg->min_uV = VDDA_PLL_MIN_UV;
+	} else if (!strcmp(name, "vdda-phy")) {
+		vreg->max_uV = VDDA_PHY_MAX_UV;
+		vreg->min_uV = VDDA_PHY_MIN_UV;
+	} else if (!strcmp(name, "vddp-ref-clk")) {
+		vreg->max_uV = VDDP_REF_CLK_MAX_UV;
+		vreg->min_uV = VDDP_REF_CLK_MIN_UV;
+	}
+
+out:
+	if (err)
+		kfree(vreg->name);
+	return err;
+}
+
+static int ufs_qcom_phy_init_vreg(struct phy *phy,
+			struct ufs_qcom_phy_vreg *vreg, const char *name)
+{
+	return __ufs_qcom_phy_init_vreg(phy, vreg, name, false);
+}
+
+static
+int ufs_qcom_phy_cfg_vreg(struct phy *phy,
+			  struct ufs_qcom_phy_vreg *vreg, bool on)
+{
+	int ret = 0;
+	struct regulator *reg = vreg->reg;
+	const char *name = vreg->name;
+	int min_uV;
+	int uA_load;
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
+	struct device *dev = ufs_qcom_phy->dev;
+
+	BUG_ON(!vreg);
+
+	if (regulator_count_voltages(reg) > 0) {
+		min_uV = on ? vreg->min_uV : 0;
+		ret = regulator_set_voltage(reg, min_uV, vreg->max_uV);
+		if (ret) {
+			dev_err(dev, "%s: %s set voltage failed, err=%d\n",
+					__func__, name, ret);
+			goto out;
+		}
+		uA_load = on ? vreg->max_uA : 0;
+		ret = regulator_set_optimum_mode(reg, uA_load);
+		if (ret >= 0) {
+			/*
+			 * regulator_set_optimum_mode() returns new regulator
+			 * mode upon success.
+			 */
+			ret = 0;
+		} else {
+			dev_err(dev, "%s: %s set optimum mode(uA_load=%d) failed, err=%d\n",
+					__func__, name, uA_load, ret);
+			goto out;
+		}
+	}
+out:
+	return ret;
+}
+
+static
+int ufs_qcom_phy_enable_vreg(struct phy *phy,
+			     struct ufs_qcom_phy_vreg *vreg)
+{
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
+	struct device *dev = ufs_qcom_phy->dev;
+	int ret = 0;
+
+	if (!vreg || vreg->enabled)
+		goto out;
+
+	ret = ufs_qcom_phy_cfg_vreg(phy, vreg, true);
+	if (ret) {
+		dev_err(dev, "%s: ufs_qcom_phy_cfg_vreg() failed, err=%d\n",
+			__func__, ret);
+		goto out;
+	}
+
+	ret = regulator_enable(vreg->reg);
+	if (ret) {
+		dev_err(dev, "%s: enable failed, err=%d\n",
+				__func__, ret);
+		goto out;
+	}
+
+	vreg->enabled = true;
+out:
+	return ret;
+}
+
+int ufs_qcom_phy_enable_ref_clk(struct phy *generic_phy)
+{
+	int ret = 0;
+	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
+
+	if (phy->is_ref_clk_enabled)
+		goto out;
+
+	/*
+	 * reference clock is propagated in a daisy-chained manner from
+	 * source to phy, so ungate them at each stage.
+	 */
+	ret = clk_prepare_enable(phy->ref_clk_src);
+	if (ret) {
+		dev_err(phy->dev, "%s: ref_clk_src enable failed %d\n",
+				__func__, ret);
+		goto out;
+	}
+
+	/*
+	 * "ref_clk_parent" is optional clock hence make sure that clk reference
+	 * is available before trying to enable the clock.
+	 */
+	if (phy->ref_clk_parent) {
+		ret = clk_prepare_enable(phy->ref_clk_parent);
+		if (ret) {
+			dev_err(phy->dev, "%s: ref_clk_parent enable failed %d\n",
+					__func__, ret);
+			goto out_disable_src;
+		}
+	}
+
+	ret = clk_prepare_enable(phy->ref_clk);
+	if (ret) {
+		dev_err(phy->dev, "%s: ref_clk enable failed %d\n",
+				__func__, ret);
+		goto out_disable_parent;
+	}
+
+	phy->is_ref_clk_enabled = true;
+	goto out;
+
+out_disable_parent:
+	if (phy->ref_clk_parent)
+		clk_disable_unprepare(phy->ref_clk_parent);
+out_disable_src:
+	clk_disable_unprepare(phy->ref_clk_src);
+out:
+	return ret;
+}
+
+static
+int ufs_qcom_phy_disable_vreg(struct phy *phy,
+			      struct ufs_qcom_phy_vreg *vreg)
+{
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
+	struct device *dev = ufs_qcom_phy->dev;
+	int ret = 0;
+
+	if (!vreg || !vreg->enabled || vreg->is_always_on)
+		goto out;
+
+	ret = regulator_disable(vreg->reg);
+
+	if (!ret) {
+		/* ignore errors on applying disable config */
+		ufs_qcom_phy_cfg_vreg(phy, vreg, false);
+		vreg->enabled = false;
+	} else {
+		dev_err(dev, "%s: %s disable failed, err=%d\n",
+				__func__, vreg->name, ret);
+	}
+out:
+	return ret;
+}
+
+void ufs_qcom_phy_disable_ref_clk(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
+
+	if (phy->is_ref_clk_enabled) {
+		clk_disable_unprepare(phy->ref_clk);
+		/*
+		 * "ref_clk_parent" is optional clock hence make sure that clk
+		 * reference is available before trying to disable the clock.
+		 */
+		if (phy->ref_clk_parent)
+			clk_disable_unprepare(phy->ref_clk_parent);
+		clk_disable_unprepare(phy->ref_clk_src);
+		phy->is_ref_clk_enabled = false;
+	}
+}
+
+#define UFS_REF_CLK_EN	(1 << 5)
+
+static void ufs_qcom_phy_dev_ref_clk_ctrl(struct phy *generic_phy, bool enable)
+{
+	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
+
+	if (phy->dev_ref_clk_ctrl_mmio &&
+	    (enable ^ phy->is_dev_ref_clk_enabled)) {
+		u32 temp = readl_relaxed(phy->dev_ref_clk_ctrl_mmio);
+
+		if (enable)
+			temp |= UFS_REF_CLK_EN;
+		else
+			temp &= ~UFS_REF_CLK_EN;
+
+		/*
+		 * If we are here to disable this clock immediately after
+		 * entering into hibern8, we need to make sure that device
+		 * ref_clk is active atleast 1us after the hibern8 enter.
+		 */
+		if (!enable)
+			udelay(1);
+
+		writel_relaxed(temp, phy->dev_ref_clk_ctrl_mmio);
+		/* ensure that ref_clk is enabled/disabled before we return */
+		wmb();
+		/*
+		 * If we call hibern8 exit after this, we need to make sure that
+		 * device ref_clk is stable for atleast 1us before the hibern8
+		 * exit command.
+		 */
+		if (enable)
+			udelay(1);
+
+		phy->is_dev_ref_clk_enabled = enable;
+	}
+}
+
+void ufs_qcom_phy_enable_dev_ref_clk(struct phy *generic_phy)
+{
+	ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, true);
+}
+
+void ufs_qcom_phy_disable_dev_ref_clk(struct phy *generic_phy)
+{
+	ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, false);
+}
+
+/* Turn ON M-PHY RMMI interface clocks */
+int ufs_qcom_phy_enable_iface_clk(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
+	int ret = 0;
+
+	if (phy->is_iface_clk_enabled)
+		goto out;
+
+	ret = clk_prepare_enable(phy->tx_iface_clk);
+	if (ret) {
+		dev_err(phy->dev, "%s: tx_iface_clk enable failed %d\n",
+				__func__, ret);
+		goto out;
+	}
+	ret = clk_prepare_enable(phy->rx_iface_clk);
+	if (ret) {
+		clk_disable_unprepare(phy->tx_iface_clk);
+		dev_err(phy->dev, "%s: rx_iface_clk enable failed %d. disabling also tx_iface_clk\n",
+				__func__, ret);
+		goto out;
+	}
+	phy->is_iface_clk_enabled = true;
+
+out:
+	return ret;
+}
+
+/* Turn OFF M-PHY RMMI interface clocks */
+void ufs_qcom_phy_disable_iface_clk(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
+
+	if (phy->is_iface_clk_enabled) {
+		clk_disable_unprepare(phy->tx_iface_clk);
+		clk_disable_unprepare(phy->rx_iface_clk);
+		phy->is_iface_clk_enabled = false;
+	}
+}
+
+int ufs_qcom_phy_start_serdes(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+	int ret = 0;
+
+	if (!ufs_qcom_phy->phy_spec_ops->start_serdes) {
+		dev_err(ufs_qcom_phy->dev, "%s: start_serdes() callback is not supported\n",
+			__func__);
+		ret = -ENOTSUPP;
+	} else {
+		ufs_qcom_phy->phy_spec_ops->start_serdes(ufs_qcom_phy);
+	}
+
+	return ret;
+}
+
+int ufs_qcom_phy_set_tx_lane_enable(struct phy *generic_phy, u32 tx_lanes)
+{
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+	int ret = 0;
+
+	if (!ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable) {
+		dev_err(ufs_qcom_phy->dev, "%s: set_tx_lane_enable() callback is not supported\n",
+			__func__);
+		ret = -ENOTSUPP;
+	} else {
+		ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable(ufs_qcom_phy,
+							       tx_lanes);
+	}
+
+	return ret;
+}
+
+void ufs_qcom_phy_save_controller_version(struct phy *generic_phy,
+					  u8 major, u16 minor, u16 step)
+{
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+
+	ufs_qcom_phy->host_ctrl_rev_major = major;
+	ufs_qcom_phy->host_ctrl_rev_minor = minor;
+	ufs_qcom_phy->host_ctrl_rev_step = step;
+}
+
+int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy, bool is_rate_B)
+{
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+	int ret = 0;
+
+	if (!ufs_qcom_phy->phy_spec_ops->calibrate_phy) {
+		dev_err(ufs_qcom_phy->dev, "%s: calibrate_phy() callback is not supported\n",
+			__func__);
+		ret = -ENOTSUPP;
+	} else {
+		ret = ufs_qcom_phy->phy_spec_ops->
+				calibrate_phy(ufs_qcom_phy, is_rate_B);
+		if (ret)
+			dev_err(ufs_qcom_phy->dev, "%s: calibrate_phy() failed %d\n",
+				__func__, ret);
+	}
+
+	return ret;
+}
+
+int ufs_qcom_phy_remove(struct phy *generic_phy,
+			struct ufs_qcom_phy *ufs_qcom_phy)
+{
+	phy_power_off(generic_phy);
+
+	kfree(ufs_qcom_phy->vdda_pll.name);
+	kfree(ufs_qcom_phy->vdda_phy.name);
+
+	return 0;
+}
+
+int ufs_qcom_phy_exit(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+
+	if (ufs_qcom_phy->is_powered_on)
+		phy_power_off(generic_phy);
+
+	return 0;
+}
+
+int ufs_qcom_phy_is_pcs_ready(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+
+	if (!ufs_qcom_phy->phy_spec_ops->is_physical_coding_sublayer_ready) {
+		dev_err(ufs_qcom_phy->dev, "%s: is_physical_coding_sublayer_ready() callback is not supported\n",
+			__func__);
+		return -ENOTSUPP;
+	}
+
+	return ufs_qcom_phy->phy_spec_ops->
+			is_physical_coding_sublayer_ready(ufs_qcom_phy);
+}
+
+int ufs_qcom_phy_power_on(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
+	struct device *dev = phy_common->dev;
+	int err;
+
+	err = ufs_qcom_phy_enable_vreg(generic_phy, &phy_common->vdda_phy);
+	if (err) {
+		dev_err(dev, "%s enable vdda_phy failed, err=%d\n",
+			__func__, err);
+		goto out;
+	}
+
+	phy_common->phy_spec_ops->power_control(phy_common, true);
+
+	/* vdda_pll also enables ref clock LDOs so enable it first */
+	err = ufs_qcom_phy_enable_vreg(generic_phy, &phy_common->vdda_pll);
+	if (err) {
+		dev_err(dev, "%s enable vdda_pll failed, err=%d\n",
+			__func__, err);
+		goto out_disable_phy;
+	}
+
+	err = ufs_qcom_phy_enable_ref_clk(generic_phy);
+	if (err) {
+		dev_err(dev, "%s enable phy ref clock failed, err=%d\n",
+			__func__, err);
+		goto out_disable_pll;
+	}
+
+	/* enable device PHY ref_clk pad rail */
+	if (phy_common->vddp_ref_clk.reg) {
+		err = ufs_qcom_phy_enable_vreg(generic_phy,
+					       &phy_common->vddp_ref_clk);
+		if (err) {
+			dev_err(dev, "%s enable vddp_ref_clk failed, err=%d\n",
+				__func__, err);
+			goto out_disable_ref_clk;
+		}
+	}
+
+	phy_common->is_powered_on = true;
+	goto out;
+
+out_disable_ref_clk:
+	ufs_qcom_phy_disable_ref_clk(generic_phy);
+out_disable_pll:
+	ufs_qcom_phy_disable_vreg(generic_phy, &phy_common->vdda_pll);
+out_disable_phy:
+	ufs_qcom_phy_disable_vreg(generic_phy, &phy_common->vdda_phy);
+out:
+	return err;
+}
+
+int ufs_qcom_phy_power_off(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
+
+	phy_common->phy_spec_ops->power_control(phy_common, false);
+
+	if (phy_common->vddp_ref_clk.reg)
+		ufs_qcom_phy_disable_vreg(generic_phy,
+					  &phy_common->vddp_ref_clk);
+	ufs_qcom_phy_disable_ref_clk(generic_phy);
+
+	ufs_qcom_phy_disable_vreg(generic_phy, &phy_common->vdda_pll);
+	ufs_qcom_phy_disable_vreg(generic_phy, &phy_common->vdda_phy);
+	phy_common->is_powered_on = false;
+
+	return 0;
+}
-- 
1.8.5.2

-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v7 1/5] phy: qcom-ufs: add support for QUALCOMM Technologies UFS PHY drivers
@ 2015-01-15 14:32   ` Yaniv Gardi
  0 siblings, 0 replies; 27+ messages in thread
From: Yaniv Gardi @ 2015-01-15 14:32 UTC (permalink / raw)
  To: James.Bottomley, hch
  Cc: linux-kernel, linux-scsi, linux-arm-msm, santoshsy,
	linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Kishon Vijay Abraham I, Grant Likely, Rob Herring,
	open list:OPEN FIRMWARE AND...

This change adds a generic and common API support for ufs phy QUALCOMM
Technologies. This support provides common code and also points
to specific phy callbacks to differentiate between different behaviors
of frequent use-cases (like power on, power off, phy calibration etc).

Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>

---
 drivers/phy/Kconfig          |   7 +
 drivers/phy/Makefile         |   1 +
 drivers/phy/phy-qcom-ufs-i.h | 118 +++++++
 drivers/phy/phy-qcom-ufs.c   | 745 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 871 insertions(+)
 create mode 100644 drivers/phy/phy-qcom-ufs-i.h
 create mode 100644 drivers/phy/phy-qcom-ufs.c

diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index ccad880..26a7623 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -277,4 +277,11 @@ config PHY_STIH41X_USB
 	  Enable this to support the USB transceiver that is part of
 	  STMicroelectronics STiH41x SoC series.
 
+config PHY_QCOM_UFS
+	tristate "Qualcomm UFS PHY driver"
+	depends on OF && ARCH_MSM
+	select GENERIC_PHY
+	help
+	  Support for UFS PHY on QCOM chipsets.
+
 endmenu
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index aa74f96..335965d 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -34,3 +34,4 @@ 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_STIH41X_USB)		+= phy-stih41x-usb.o
+obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs.o
diff --git a/drivers/phy/phy-qcom-ufs-i.h b/drivers/phy/phy-qcom-ufs-i.h
new file mode 100644
index 0000000..dac200f
--- /dev/null
+++ b/drivers/phy/phy-qcom-ufs-i.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2013-2015, 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.
+ *
+ */
+
+#ifndef UFS_QCOM_PHY_I_H_
+#define UFS_QCOM_PHY_I_H_
+
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+
+#define UFS_QCOM_PHY_NAME_LEN	30
+
+struct ufs_qcom_phy_calibration {
+	u32 reg_offset;
+	u32 cfg_value;
+};
+
+struct ufs_qcom_phy_vreg {
+	const char *name;
+	struct regulator *reg;
+	int max_uA;
+	int min_uV;
+	int max_uV;
+	bool enabled;
+	bool is_always_on;
+};
+
+struct ufs_qcom_phy {
+	struct list_head list;
+	struct device *dev;
+	void __iomem *mmio;
+	void __iomem *dev_ref_clk_ctrl_mmio;
+	struct clk *tx_iface_clk;
+	struct clk *rx_iface_clk;
+	bool is_iface_clk_enabled;
+	struct clk *ref_clk_src;
+	struct clk *ref_clk_parent;
+	struct clk *ref_clk;
+	bool is_ref_clk_enabled;
+	bool is_dev_ref_clk_enabled;
+	struct ufs_qcom_phy_vreg vdda_pll;
+	struct ufs_qcom_phy_vreg vdda_phy;
+	struct ufs_qcom_phy_vreg vddp_ref_clk;
+	unsigned int quirks;
+
+	/*
+	* If UFS link is put into Hibern8 and if UFS PHY analog hardware is
+	* power collapsed (by clearing UFS_PHY_POWER_DOWN_CONTROL), Hibern8
+	* exit might fail even after powering on UFS PHY analog hardware.
+	* Enabling this quirk will help to solve above issue by doing
+	* custom PHY settings just before PHY analog power collapse.
+	*/
+	#define UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE	BIT(0)
+
+	u8 host_ctrl_rev_major;
+	u16 host_ctrl_rev_minor;
+	u16 host_ctrl_rev_step;
+
+	char name[UFS_QCOM_PHY_NAME_LEN];
+	struct ufs_qcom_phy_calibration *cached_regs;
+	int cached_regs_table_size;
+	bool is_powered_on;
+	struct ufs_qcom_phy_specific_ops *phy_spec_ops;
+};
+
+/**
+ * struct ufs_qcom_phy_specific_ops - set of pointers to functions which have a
+ * specific implementation per phy. Each UFS phy, should implement
+ * those functions according to its spec and requirements
+ * @calibrate_phy: pointer to a function that calibrate the phy
+ * @start_serdes: pointer to a function that starts the serdes
+ * @is_physical_coding_sublayer_ready: pointer to a function that
+ * checks pcs readiness. returns 0 for success and non-zero for error.
+ * @set_tx_lane_enable: pointer to a function that enable tx lanes
+ * @power_control: pointer to a function that controls analog rail of phy
+ * and writes to QSERDES_RX_SIGDET_CNTRL attribute
+ */
+struct ufs_qcom_phy_specific_ops {
+	int (*calibrate_phy)(struct ufs_qcom_phy *phy, bool is_rate_B);
+	void (*start_serdes)(struct ufs_qcom_phy *phy);
+	int (*is_physical_coding_sublayer_ready)(struct ufs_qcom_phy *phy);
+	void (*set_tx_lane_enable)(struct ufs_qcom_phy *phy, u32 val);
+	void (*power_control)(struct ufs_qcom_phy *phy, bool val);
+};
+
+struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy);
+int ufs_qcom_phy_power_on(struct phy *generic_phy);
+int ufs_qcom_phy_power_off(struct phy *generic_phy);
+int ufs_qcom_phy_exit(struct phy *generic_phy);
+int ufs_qcom_phy_init_clks(struct phy *generic_phy,
+			struct ufs_qcom_phy *phy_common);
+int ufs_qcom_phy_init_vregulators(struct phy *generic_phy,
+			struct ufs_qcom_phy *phy_common);
+int ufs_qcom_phy_remove(struct phy *generic_phy,
+		       struct ufs_qcom_phy *ufs_qcom_phy);
+struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
+			struct ufs_qcom_phy *common_cfg,
+			struct phy_ops *ufs_qcom_phy_gen_ops,
+			struct ufs_qcom_phy_specific_ops *phy_spec_ops);
+int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
+			struct ufs_qcom_phy_calibration *tbl_A, int tbl_size_A,
+			struct ufs_qcom_phy_calibration *tbl_B, int tbl_size_B,
+			bool is_rate_B);
+#endif
diff --git a/drivers/phy/phy-qcom-ufs.c b/drivers/phy/phy-qcom-ufs.c
new file mode 100644
index 0000000..44ee983
--- /dev/null
+++ b/drivers/phy/phy-qcom-ufs.c
@@ -0,0 +1,745 @@
+/*
+ * Copyright (c) 2013-2015, 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 "phy-qcom-ufs-i.h"
+
+#define MAX_PROP_NAME              32
+#define VDDA_PHY_MIN_UV            1000000
+#define VDDA_PHY_MAX_UV            1000000
+#define VDDA_PLL_MIN_UV            1800000
+#define VDDA_PLL_MAX_UV            1800000
+#define VDDP_REF_CLK_MIN_UV        1200000
+#define VDDP_REF_CLK_MAX_UV        1200000
+
+static int __ufs_qcom_phy_init_vreg(struct phy *, struct ufs_qcom_phy_vreg *,
+				    const char *, bool);
+static int ufs_qcom_phy_init_vreg(struct phy *, struct ufs_qcom_phy_vreg *,
+				  const char *);
+static int ufs_qcom_phy_base_init(struct platform_device *pdev,
+				  struct ufs_qcom_phy *phy_common);
+
+int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
+			   struct ufs_qcom_phy_calibration *tbl_A,
+			   int tbl_size_A,
+			   struct ufs_qcom_phy_calibration *tbl_B,
+			   int tbl_size_B, bool is_rate_B)
+{
+	int i;
+	int ret = 0;
+
+	if (!tbl_A) {
+		dev_err(ufs_qcom_phy->dev, "%s: tbl_A is NULL", __func__);
+		ret = EINVAL;
+		goto out;
+	}
+
+	for (i = 0; i < tbl_size_A; i++)
+		writel_relaxed(tbl_A[i].cfg_value,
+			       ufs_qcom_phy->mmio + tbl_A[i].reg_offset);
+
+	/*
+	 * In case we would like to work in rate B, we need
+	 * to override a registers that were configured in rate A table
+	 * with registers of rate B table.
+	 * table.
+	 */
+	if (is_rate_B) {
+		if (!tbl_B) {
+			dev_err(ufs_qcom_phy->dev, "%s: tbl_B is NULL",
+				__func__);
+			ret = EINVAL;
+			goto out;
+		}
+
+		for (i = 0; i < tbl_size_B; i++)
+			writel_relaxed(tbl_B[i].cfg_value,
+				ufs_qcom_phy->mmio + tbl_B[i].reg_offset);
+	}
+
+	/* flush buffered writes */
+	mb();
+
+out:
+	return ret;
+}
+
+struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
+				struct ufs_qcom_phy *common_cfg,
+				struct phy_ops *ufs_qcom_phy_gen_ops,
+				struct ufs_qcom_phy_specific_ops *phy_spec_ops)
+{
+	int err;
+	struct device *dev = &pdev->dev;
+	struct phy *generic_phy = NULL;
+	struct phy_provider *phy_provider;
+
+	err = ufs_qcom_phy_base_init(pdev, common_cfg);
+	if (err) {
+		dev_err(dev, "%s: phy base init failed %d\n", __func__, err);
+		goto out;
+	}
+
+	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+	if (IS_ERR(phy_provider)) {
+		err = PTR_ERR(phy_provider);
+		dev_err(dev, "%s: failed to register phy %d\n", __func__, err);
+		goto out;
+	}
+
+	generic_phy = devm_phy_create(dev, NULL, ufs_qcom_phy_gen_ops);
+	if (IS_ERR(generic_phy)) {
+		err =  PTR_ERR(generic_phy);
+		dev_err(dev, "%s: failed to create phy %d\n", __func__, err);
+		goto out;
+	}
+
+	common_cfg->phy_spec_ops = phy_spec_ops;
+	common_cfg->dev = dev;
+
+out:
+	return generic_phy;
+}
+
+/*
+ * This assumes the embedded phy structure inside generic_phy is of type
+ * struct ufs_qcom_phy. In order to function properly it's crucial
+ * to keep the embedded struct "struct ufs_qcom_phy common_cfg"
+ * as the first inside generic_phy.
+ */
+struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy)
+{
+	return (struct ufs_qcom_phy *)phy_get_drvdata(generic_phy);
+}
+
+static
+int ufs_qcom_phy_base_init(struct platform_device *pdev,
+			   struct ufs_qcom_phy *phy_common)
+{
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	int err = 0;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_mem");
+	if (!res) {
+		dev_err(dev, "%s: phy_mem resource not found\n", __func__);
+		err = -ENOMEM;
+		goto out;
+	}
+
+	phy_common->mmio = devm_ioremap_resource(dev, res);
+	if (IS_ERR((void const *)phy_common->mmio)) {
+		err = PTR_ERR((void const *)phy_common->mmio);
+		phy_common->mmio = NULL;
+		dev_err(dev, "%s: ioremap for phy_mem resource failed %d\n",
+			__func__, err);
+		goto out;
+	}
+
+	/* "dev_ref_clk_ctrl_mem" is optional resource */
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "dev_ref_clk_ctrl_mem");
+	if (!res) {
+		dev_dbg(dev, "%s: dev_ref_clk_ctrl_mem resource not found\n",
+			__func__);
+		goto out;
+	}
+
+	phy_common->dev_ref_clk_ctrl_mmio = devm_ioremap_resource(dev, res);
+	if (IS_ERR((void const *)phy_common->dev_ref_clk_ctrl_mmio)) {
+		err = PTR_ERR((void const *)phy_common->dev_ref_clk_ctrl_mmio);
+		phy_common->dev_ref_clk_ctrl_mmio = NULL;
+		dev_err(dev, "%s: ioremap for dev_ref_clk_ctrl_mem resource failed %d\n",
+			__func__, err);
+	}
+
+out:
+	return err;
+}
+
+static int __ufs_qcom_phy_clk_get(struct phy *phy,
+			 const char *name, struct clk **clk_out, bool err_print)
+{
+	struct clk *clk;
+	int err = 0;
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
+	struct device *dev = ufs_qcom_phy->dev;
+
+	clk = devm_clk_get(dev, name);
+	if (IS_ERR(clk)) {
+		err = PTR_ERR(clk);
+		if (err_print)
+			dev_err(dev, "failed to get %s err %d", name, err);
+	} else {
+		*clk_out = clk;
+	}
+
+	return err;
+}
+
+static
+int ufs_qcom_phy_clk_get(struct phy *phy,
+			 const char *name, struct clk **clk_out)
+{
+	return __ufs_qcom_phy_clk_get(phy, name, clk_out, true);
+}
+
+int
+ufs_qcom_phy_init_clks(struct phy *generic_phy,
+		       struct ufs_qcom_phy *phy_common)
+{
+	int err;
+
+	err = ufs_qcom_phy_clk_get(generic_phy, "tx_iface_clk",
+				   &phy_common->tx_iface_clk);
+	if (err)
+		goto out;
+
+	err = ufs_qcom_phy_clk_get(generic_phy, "rx_iface_clk",
+				   &phy_common->rx_iface_clk);
+	if (err)
+		goto out;
+
+	err = ufs_qcom_phy_clk_get(generic_phy, "ref_clk_src",
+				   &phy_common->ref_clk_src);
+	if (err)
+		goto out;
+
+	/*
+	 * "ref_clk_parent" is optional hence don't abort init if it's not
+	 * found.
+	 */
+	__ufs_qcom_phy_clk_get(generic_phy, "ref_clk_parent",
+				   &phy_common->ref_clk_parent, false);
+
+	err = ufs_qcom_phy_clk_get(generic_phy, "ref_clk",
+				   &phy_common->ref_clk);
+
+out:
+	return err;
+}
+
+int
+ufs_qcom_phy_init_vregulators(struct phy *generic_phy,
+			      struct ufs_qcom_phy *phy_common)
+{
+	int err;
+
+	err = ufs_qcom_phy_init_vreg(generic_phy, &phy_common->vdda_pll,
+		"vdda-pll");
+	if (err)
+		goto out;
+
+	err = ufs_qcom_phy_init_vreg(generic_phy, &phy_common->vdda_phy,
+		"vdda-phy");
+
+	if (err)
+		goto out;
+
+	/* vddp-ref-clk-* properties are optional */
+	__ufs_qcom_phy_init_vreg(generic_phy, &phy_common->vddp_ref_clk,
+				 "vddp-ref-clk", true);
+out:
+	return err;
+}
+
+static int __ufs_qcom_phy_init_vreg(struct phy *phy,
+		struct ufs_qcom_phy_vreg *vreg, const char *name, bool optional)
+{
+	int err = 0;
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
+	struct device *dev = ufs_qcom_phy->dev;
+
+	char prop_name[MAX_PROP_NAME];
+
+	vreg->name = kstrdup(name, GFP_KERNEL);
+	if (!vreg->name) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	vreg->reg = devm_regulator_get(dev, name);
+	if (IS_ERR(vreg->reg)) {
+		err = PTR_ERR(vreg->reg);
+		vreg->reg = NULL;
+		if (!optional)
+			dev_err(dev, "failed to get %s, %d\n", name, err);
+		goto out;
+	}
+
+	if (dev->of_node) {
+		snprintf(prop_name, MAX_PROP_NAME, "%s-max-microamp", name);
+		err = of_property_read_u32(dev->of_node,
+					prop_name, &vreg->max_uA);
+		if (err && err != -EINVAL) {
+			dev_err(dev, "%s: failed to read %s\n",
+					__func__, prop_name);
+			goto out;
+		} else if (err == -EINVAL || !vreg->max_uA) {
+			if (regulator_count_voltages(vreg->reg) > 0) {
+				dev_err(dev, "%s: %s is mandatory\n",
+						__func__, prop_name);
+				goto out;
+			}
+			err = 0;
+		}
+		snprintf(prop_name, MAX_PROP_NAME, "%s-always-on", name);
+		if (of_get_property(dev->of_node, prop_name, NULL))
+			vreg->is_always_on = true;
+		else
+			vreg->is_always_on = false;
+	}
+
+	if (!strcmp(name, "vdda-pll")) {
+		vreg->max_uV = VDDA_PLL_MAX_UV;
+		vreg->min_uV = VDDA_PLL_MIN_UV;
+	} else if (!strcmp(name, "vdda-phy")) {
+		vreg->max_uV = VDDA_PHY_MAX_UV;
+		vreg->min_uV = VDDA_PHY_MIN_UV;
+	} else if (!strcmp(name, "vddp-ref-clk")) {
+		vreg->max_uV = VDDP_REF_CLK_MAX_UV;
+		vreg->min_uV = VDDP_REF_CLK_MIN_UV;
+	}
+
+out:
+	if (err)
+		kfree(vreg->name);
+	return err;
+}
+
+static int ufs_qcom_phy_init_vreg(struct phy *phy,
+			struct ufs_qcom_phy_vreg *vreg, const char *name)
+{
+	return __ufs_qcom_phy_init_vreg(phy, vreg, name, false);
+}
+
+static
+int ufs_qcom_phy_cfg_vreg(struct phy *phy,
+			  struct ufs_qcom_phy_vreg *vreg, bool on)
+{
+	int ret = 0;
+	struct regulator *reg = vreg->reg;
+	const char *name = vreg->name;
+	int min_uV;
+	int uA_load;
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
+	struct device *dev = ufs_qcom_phy->dev;
+
+	BUG_ON(!vreg);
+
+	if (regulator_count_voltages(reg) > 0) {
+		min_uV = on ? vreg->min_uV : 0;
+		ret = regulator_set_voltage(reg, min_uV, vreg->max_uV);
+		if (ret) {
+			dev_err(dev, "%s: %s set voltage failed, err=%d\n",
+					__func__, name, ret);
+			goto out;
+		}
+		uA_load = on ? vreg->max_uA : 0;
+		ret = regulator_set_optimum_mode(reg, uA_load);
+		if (ret >= 0) {
+			/*
+			 * regulator_set_optimum_mode() returns new regulator
+			 * mode upon success.
+			 */
+			ret = 0;
+		} else {
+			dev_err(dev, "%s: %s set optimum mode(uA_load=%d) failed, err=%d\n",
+					__func__, name, uA_load, ret);
+			goto out;
+		}
+	}
+out:
+	return ret;
+}
+
+static
+int ufs_qcom_phy_enable_vreg(struct phy *phy,
+			     struct ufs_qcom_phy_vreg *vreg)
+{
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
+	struct device *dev = ufs_qcom_phy->dev;
+	int ret = 0;
+
+	if (!vreg || vreg->enabled)
+		goto out;
+
+	ret = ufs_qcom_phy_cfg_vreg(phy, vreg, true);
+	if (ret) {
+		dev_err(dev, "%s: ufs_qcom_phy_cfg_vreg() failed, err=%d\n",
+			__func__, ret);
+		goto out;
+	}
+
+	ret = regulator_enable(vreg->reg);
+	if (ret) {
+		dev_err(dev, "%s: enable failed, err=%d\n",
+				__func__, ret);
+		goto out;
+	}
+
+	vreg->enabled = true;
+out:
+	return ret;
+}
+
+int ufs_qcom_phy_enable_ref_clk(struct phy *generic_phy)
+{
+	int ret = 0;
+	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
+
+	if (phy->is_ref_clk_enabled)
+		goto out;
+
+	/*
+	 * reference clock is propagated in a daisy-chained manner from
+	 * source to phy, so ungate them at each stage.
+	 */
+	ret = clk_prepare_enable(phy->ref_clk_src);
+	if (ret) {
+		dev_err(phy->dev, "%s: ref_clk_src enable failed %d\n",
+				__func__, ret);
+		goto out;
+	}
+
+	/*
+	 * "ref_clk_parent" is optional clock hence make sure that clk reference
+	 * is available before trying to enable the clock.
+	 */
+	if (phy->ref_clk_parent) {
+		ret = clk_prepare_enable(phy->ref_clk_parent);
+		if (ret) {
+			dev_err(phy->dev, "%s: ref_clk_parent enable failed %d\n",
+					__func__, ret);
+			goto out_disable_src;
+		}
+	}
+
+	ret = clk_prepare_enable(phy->ref_clk);
+	if (ret) {
+		dev_err(phy->dev, "%s: ref_clk enable failed %d\n",
+				__func__, ret);
+		goto out_disable_parent;
+	}
+
+	phy->is_ref_clk_enabled = true;
+	goto out;
+
+out_disable_parent:
+	if (phy->ref_clk_parent)
+		clk_disable_unprepare(phy->ref_clk_parent);
+out_disable_src:
+	clk_disable_unprepare(phy->ref_clk_src);
+out:
+	return ret;
+}
+
+static
+int ufs_qcom_phy_disable_vreg(struct phy *phy,
+			      struct ufs_qcom_phy_vreg *vreg)
+{
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
+	struct device *dev = ufs_qcom_phy->dev;
+	int ret = 0;
+
+	if (!vreg || !vreg->enabled || vreg->is_always_on)
+		goto out;
+
+	ret = regulator_disable(vreg->reg);
+
+	if (!ret) {
+		/* ignore errors on applying disable config */
+		ufs_qcom_phy_cfg_vreg(phy, vreg, false);
+		vreg->enabled = false;
+	} else {
+		dev_err(dev, "%s: %s disable failed, err=%d\n",
+				__func__, vreg->name, ret);
+	}
+out:
+	return ret;
+}
+
+void ufs_qcom_phy_disable_ref_clk(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
+
+	if (phy->is_ref_clk_enabled) {
+		clk_disable_unprepare(phy->ref_clk);
+		/*
+		 * "ref_clk_parent" is optional clock hence make sure that clk
+		 * reference is available before trying to disable the clock.
+		 */
+		if (phy->ref_clk_parent)
+			clk_disable_unprepare(phy->ref_clk_parent);
+		clk_disable_unprepare(phy->ref_clk_src);
+		phy->is_ref_clk_enabled = false;
+	}
+}
+
+#define UFS_REF_CLK_EN	(1 << 5)
+
+static void ufs_qcom_phy_dev_ref_clk_ctrl(struct phy *generic_phy, bool enable)
+{
+	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
+
+	if (phy->dev_ref_clk_ctrl_mmio &&
+	    (enable ^ phy->is_dev_ref_clk_enabled)) {
+		u32 temp = readl_relaxed(phy->dev_ref_clk_ctrl_mmio);
+
+		if (enable)
+			temp |= UFS_REF_CLK_EN;
+		else
+			temp &= ~UFS_REF_CLK_EN;
+
+		/*
+		 * If we are here to disable this clock immediately after
+		 * entering into hibern8, we need to make sure that device
+		 * ref_clk is active atleast 1us after the hibern8 enter.
+		 */
+		if (!enable)
+			udelay(1);
+
+		writel_relaxed(temp, phy->dev_ref_clk_ctrl_mmio);
+		/* ensure that ref_clk is enabled/disabled before we return */
+		wmb();
+		/*
+		 * If we call hibern8 exit after this, we need to make sure that
+		 * device ref_clk is stable for atleast 1us before the hibern8
+		 * exit command.
+		 */
+		if (enable)
+			udelay(1);
+
+		phy->is_dev_ref_clk_enabled = enable;
+	}
+}
+
+void ufs_qcom_phy_enable_dev_ref_clk(struct phy *generic_phy)
+{
+	ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, true);
+}
+
+void ufs_qcom_phy_disable_dev_ref_clk(struct phy *generic_phy)
+{
+	ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, false);
+}
+
+/* Turn ON M-PHY RMMI interface clocks */
+int ufs_qcom_phy_enable_iface_clk(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
+	int ret = 0;
+
+	if (phy->is_iface_clk_enabled)
+		goto out;
+
+	ret = clk_prepare_enable(phy->tx_iface_clk);
+	if (ret) {
+		dev_err(phy->dev, "%s: tx_iface_clk enable failed %d\n",
+				__func__, ret);
+		goto out;
+	}
+	ret = clk_prepare_enable(phy->rx_iface_clk);
+	if (ret) {
+		clk_disable_unprepare(phy->tx_iface_clk);
+		dev_err(phy->dev, "%s: rx_iface_clk enable failed %d. disabling also tx_iface_clk\n",
+				__func__, ret);
+		goto out;
+	}
+	phy->is_iface_clk_enabled = true;
+
+out:
+	return ret;
+}
+
+/* Turn OFF M-PHY RMMI interface clocks */
+void ufs_qcom_phy_disable_iface_clk(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
+
+	if (phy->is_iface_clk_enabled) {
+		clk_disable_unprepare(phy->tx_iface_clk);
+		clk_disable_unprepare(phy->rx_iface_clk);
+		phy->is_iface_clk_enabled = false;
+	}
+}
+
+int ufs_qcom_phy_start_serdes(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+	int ret = 0;
+
+	if (!ufs_qcom_phy->phy_spec_ops->start_serdes) {
+		dev_err(ufs_qcom_phy->dev, "%s: start_serdes() callback is not supported\n",
+			__func__);
+		ret = -ENOTSUPP;
+	} else {
+		ufs_qcom_phy->phy_spec_ops->start_serdes(ufs_qcom_phy);
+	}
+
+	return ret;
+}
+
+int ufs_qcom_phy_set_tx_lane_enable(struct phy *generic_phy, u32 tx_lanes)
+{
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+	int ret = 0;
+
+	if (!ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable) {
+		dev_err(ufs_qcom_phy->dev, "%s: set_tx_lane_enable() callback is not supported\n",
+			__func__);
+		ret = -ENOTSUPP;
+	} else {
+		ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable(ufs_qcom_phy,
+							       tx_lanes);
+	}
+
+	return ret;
+}
+
+void ufs_qcom_phy_save_controller_version(struct phy *generic_phy,
+					  u8 major, u16 minor, u16 step)
+{
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+
+	ufs_qcom_phy->host_ctrl_rev_major = major;
+	ufs_qcom_phy->host_ctrl_rev_minor = minor;
+	ufs_qcom_phy->host_ctrl_rev_step = step;
+}
+
+int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy, bool is_rate_B)
+{
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+	int ret = 0;
+
+	if (!ufs_qcom_phy->phy_spec_ops->calibrate_phy) {
+		dev_err(ufs_qcom_phy->dev, "%s: calibrate_phy() callback is not supported\n",
+			__func__);
+		ret = -ENOTSUPP;
+	} else {
+		ret = ufs_qcom_phy->phy_spec_ops->
+				calibrate_phy(ufs_qcom_phy, is_rate_B);
+		if (ret)
+			dev_err(ufs_qcom_phy->dev, "%s: calibrate_phy() failed %d\n",
+				__func__, ret);
+	}
+
+	return ret;
+}
+
+int ufs_qcom_phy_remove(struct phy *generic_phy,
+			struct ufs_qcom_phy *ufs_qcom_phy)
+{
+	phy_power_off(generic_phy);
+
+	kfree(ufs_qcom_phy->vdda_pll.name);
+	kfree(ufs_qcom_phy->vdda_phy.name);
+
+	return 0;
+}
+
+int ufs_qcom_phy_exit(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+
+	if (ufs_qcom_phy->is_powered_on)
+		phy_power_off(generic_phy);
+
+	return 0;
+}
+
+int ufs_qcom_phy_is_pcs_ready(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+
+	if (!ufs_qcom_phy->phy_spec_ops->is_physical_coding_sublayer_ready) {
+		dev_err(ufs_qcom_phy->dev, "%s: is_physical_coding_sublayer_ready() callback is not supported\n",
+			__func__);
+		return -ENOTSUPP;
+	}
+
+	return ufs_qcom_phy->phy_spec_ops->
+			is_physical_coding_sublayer_ready(ufs_qcom_phy);
+}
+
+int ufs_qcom_phy_power_on(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
+	struct device *dev = phy_common->dev;
+	int err;
+
+	err = ufs_qcom_phy_enable_vreg(generic_phy, &phy_common->vdda_phy);
+	if (err) {
+		dev_err(dev, "%s enable vdda_phy failed, err=%d\n",
+			__func__, err);
+		goto out;
+	}
+
+	phy_common->phy_spec_ops->power_control(phy_common, true);
+
+	/* vdda_pll also enables ref clock LDOs so enable it first */
+	err = ufs_qcom_phy_enable_vreg(generic_phy, &phy_common->vdda_pll);
+	if (err) {
+		dev_err(dev, "%s enable vdda_pll failed, err=%d\n",
+			__func__, err);
+		goto out_disable_phy;
+	}
+
+	err = ufs_qcom_phy_enable_ref_clk(generic_phy);
+	if (err) {
+		dev_err(dev, "%s enable phy ref clock failed, err=%d\n",
+			__func__, err);
+		goto out_disable_pll;
+	}
+
+	/* enable device PHY ref_clk pad rail */
+	if (phy_common->vddp_ref_clk.reg) {
+		err = ufs_qcom_phy_enable_vreg(generic_phy,
+					       &phy_common->vddp_ref_clk);
+		if (err) {
+			dev_err(dev, "%s enable vddp_ref_clk failed, err=%d\n",
+				__func__, err);
+			goto out_disable_ref_clk;
+		}
+	}
+
+	phy_common->is_powered_on = true;
+	goto out;
+
+out_disable_ref_clk:
+	ufs_qcom_phy_disable_ref_clk(generic_phy);
+out_disable_pll:
+	ufs_qcom_phy_disable_vreg(generic_phy, &phy_common->vdda_pll);
+out_disable_phy:
+	ufs_qcom_phy_disable_vreg(generic_phy, &phy_common->vdda_phy);
+out:
+	return err;
+}
+
+int ufs_qcom_phy_power_off(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
+
+	phy_common->phy_spec_ops->power_control(phy_common, false);
+
+	if (phy_common->vddp_ref_clk.reg)
+		ufs_qcom_phy_disable_vreg(generic_phy,
+					  &phy_common->vddp_ref_clk);
+	ufs_qcom_phy_disable_ref_clk(generic_phy);
+
+	ufs_qcom_phy_disable_vreg(generic_phy, &phy_common->vdda_pll);
+	ufs_qcom_phy_disable_vreg(generic_phy, &phy_common->vdda_phy);
+	phy_common->is_powered_on = false;
+
+	return 0;
+}
-- 
1.8.5.2

-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v7 2/5] phy: qcom-ufs: add support for 20nm phy
  2015-01-15 14:32 [PATCH v7 0/5] phy: qcom-ufs: add support for QUALCOMM Yaniv Gardi
@ 2015-01-15 14:32   ` Yaniv Gardi
  2015-01-15 14:32   ` Yaniv Gardi
                     ` (4 subsequent siblings)
  5 siblings, 0 replies; 27+ messages in thread
From: Yaniv Gardi @ 2015-01-15 14:32 UTC (permalink / raw)
  To: James.Bottomley, hch
  Cc: linux-kernel, linux-scsi, linux-arm-msm, santoshsy,
	linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Kishon Vijay Abraham I, Grant Likely, Rob Herring,
	open list:OPEN FIRMWARE AND...

This change adds a support for a 20nm qcom-ufs phy that is required in
platforms that use ufs-qcom controller.

Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>

---
 drivers/phy/Makefile                |   1 +
 drivers/phy/phy-qcom-ufs-i.h        |  43 +++++-
 drivers/phy/phy-qcom-ufs-qmp-20nm.c | 257 ++++++++++++++++++++++++++++++++++++
 drivers/phy/phy-qcom-ufs-qmp-20nm.h | 235 +++++++++++++++++++++++++++++++++
 include/linux/phy/phy-qcom-ufs.h    |  59 +++++++++
 5 files changed, 594 insertions(+), 1 deletion(-)
 create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.c
 create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.h
 create mode 100644 include/linux/phy/phy-qcom-ufs.h

diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 335965d..781b2fa 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -35,3 +35,4 @@ obj-$(CONFIG_PHY_XGENE)			+= phy-xgene.o
 obj-$(CONFIG_PHY_STIH407_USB)		+= phy-stih407-usb.o
 obj-$(CONFIG_PHY_STIH41X_USB)		+= phy-stih41x-usb.o
 obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs.o
+obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs-qmp-20nm.o
diff --git a/drivers/phy/phy-qcom-ufs-i.h b/drivers/phy/phy-qcom-ufs-i.h
index dac200f..591a391 100644
--- a/drivers/phy/phy-qcom-ufs-i.h
+++ b/drivers/phy/phy-qcom-ufs-i.h
@@ -15,15 +15,56 @@
 #ifndef UFS_QCOM_PHY_I_H_
 #define UFS_QCOM_PHY_I_H_
 
+#include <linux/module.h>
 #include <linux/clk.h>
+#include <linux/regulator/consumer.h>
 #include <linux/slab.h>
-#include <linux/phy/phy.h>
+#include <linux/phy/phy-qcom-ufs.h>
 #include <linux/platform_device.h>
 #include <linux/io.h>
 #include <linux/delay.h>
 
+#define readl_poll_timeout(addr, val, cond, sleep_us, timeout_us) \
+({ \
+	ktime_t timeout = ktime_add_us(ktime_get(), timeout_us); \
+	might_sleep_if(timeout_us); \
+	for (;;) { \
+		(val) = readl(addr); \
+		if (cond) \
+			break; \
+		if (timeout_us && ktime_compare(ktime_get(), timeout) > 0) { \
+			(val) = readl(addr); \
+			break; \
+		} \
+		if (sleep_us) \
+			usleep_range(DIV_ROUND_UP(sleep_us, 4), sleep_us); \
+	} \
+	(cond) ? 0 : -ETIMEDOUT; \
+})
+
+#define UFS_QCOM_PHY_CAL_ENTRY(reg, val)	\
+	{				\
+		.reg_offset = reg,	\
+		.cfg_value = val,	\
+	}
+
 #define UFS_QCOM_PHY_NAME_LEN	30
 
+enum {
+	MASK_SERDES_START       = 0x1,
+	MASK_PCS_READY          = 0x1,
+};
+
+enum {
+	OFFSET_SERDES_START     = 0x0,
+};
+
+struct ufs_qcom_phy_stored_attributes {
+	u32 att;
+	u32 value;
+};
+
+
 struct ufs_qcom_phy_calibration {
 	u32 reg_offset;
 	u32 cfg_value;
diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.c b/drivers/phy/phy-qcom-ufs-qmp-20nm.c
new file mode 100644
index 0000000..8332f96
--- /dev/null
+++ b/drivers/phy/phy-qcom-ufs-qmp-20nm.c
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2013-2015, 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 "phy-qcom-ufs-qmp-20nm.h"
+
+#define UFS_PHY_NAME "ufs_phy_qmp_20nm"
+
+static
+int ufs_qcom_phy_qmp_20nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
+					bool is_rate_B)
+{
+	struct ufs_qcom_phy_calibration *tbl_A, *tbl_B;
+	int tbl_size_A, tbl_size_B;
+	u8 major = ufs_qcom_phy->host_ctrl_rev_major;
+	u16 minor = ufs_qcom_phy->host_ctrl_rev_minor;
+	u16 step = ufs_qcom_phy->host_ctrl_rev_step;
+	int err;
+
+	if ((major == 0x1) && (minor == 0x002) && (step == 0x0000)) {
+		tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_2_0);
+		tbl_A = phy_cal_table_rate_A_1_2_0;
+	} else if ((major == 0x1) && (minor == 0x003) && (step == 0x0000)) {
+		tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_3_0);
+		tbl_A = phy_cal_table_rate_A_1_3_0;
+	} else {
+		dev_err(ufs_qcom_phy->dev, "%s: Unknown UFS-PHY version, no calibration values\n",
+			__func__);
+		err = -ENODEV;
+		goto out;
+	}
+
+	tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
+	tbl_B = phy_cal_table_rate_B;
+
+	err = ufs_qcom_phy_calibrate(ufs_qcom_phy, tbl_A, tbl_size_A,
+						tbl_B, tbl_size_B, is_rate_B);
+
+	if (err)
+		dev_err(ufs_qcom_phy->dev, "%s: ufs_qcom_phy_calibrate() failed %d\n",
+			__func__, err);
+
+out:
+	return err;
+}
+
+static
+void ufs_qcom_phy_qmp_20nm_advertise_quirks(struct ufs_qcom_phy *phy_common)
+{
+	phy_common->quirks =
+		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
+}
+
+static int ufs_qcom_phy_qmp_20nm_init(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy_qmp_20nm *phy = phy_get_drvdata(generic_phy);
+	struct ufs_qcom_phy *phy_common = &phy->common_cfg;
+	int err = 0;
+
+	err = ufs_qcom_phy_init_clks(generic_phy, phy_common);
+	if (err) {
+		dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_clks() failed %d\n",
+			__func__, err);
+		goto out;
+	}
+
+	err = ufs_qcom_phy_init_vregulators(generic_phy, phy_common);
+	if (err) {
+		dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_vregulators() failed %d\n",
+			__func__, err);
+		goto out;
+	}
+
+	ufs_qcom_phy_qmp_20nm_advertise_quirks(phy_common);
+
+out:
+	return err;
+}
+
+static
+void ufs_qcom_phy_qmp_20nm_power_control(struct ufs_qcom_phy *phy, bool val)
+{
+	bool hibern8_exit_after_pwr_collapse = phy->quirks &
+		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
+
+	if (val) {
+		writel_relaxed(0x1, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
+		/*
+		 * Before any transactions involving PHY, ensure PHY knows
+		 * that it's analog rail is powered ON.
+		 */
+		mb();
+
+		if (hibern8_exit_after_pwr_collapse) {
+			/*
+			 * Give atleast 1us delay after restoring PHY analog
+			 * power.
+			 */
+			usleep_range(1, 2);
+			writel_relaxed(0x0A, phy->mmio +
+				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
+			writel_relaxed(0x08, phy->mmio +
+				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
+			/*
+			 * Make sure workaround is deactivated before proceeding
+			 * with normal PHY operations.
+			 */
+			mb();
+		}
+	} else {
+		if (hibern8_exit_after_pwr_collapse) {
+			writel_relaxed(0x0A, phy->mmio +
+				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
+			writel_relaxed(0x02, phy->mmio +
+				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
+			/*
+			 * Make sure that above workaround is activated before
+			 * PHY analog power collapse.
+			 */
+			mb();
+		}
+
+		writel_relaxed(0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
+		/*
+		 * ensure that PHY knows its PHY analog rail is going
+		 * to be powered down
+		 */
+		mb();
+	}
+}
+
+static
+void ufs_qcom_phy_qmp_20nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val)
+{
+	writel_relaxed(val & UFS_PHY_TX_LANE_ENABLE_MASK,
+			phy->mmio + UFS_PHY_TX_LANE_ENABLE);
+	mb();
+}
+
+static inline void ufs_qcom_phy_qmp_20nm_start_serdes(struct ufs_qcom_phy *phy)
+{
+	u32 tmp;
+
+	tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
+	tmp &= ~MASK_SERDES_START;
+	tmp |= (1 << OFFSET_SERDES_START);
+	writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
+	mb();
+}
+
+static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
+{
+	int err = 0;
+	u32 val;
+
+	err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS,
+			val, (val & MASK_PCS_READY), 10, 1000000);
+	if (err)
+		dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n",
+			__func__, err);
+	return err;
+}
+
+static struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
+	.init		= ufs_qcom_phy_qmp_20nm_init,
+	.exit		= ufs_qcom_phy_exit,
+	.power_on	= ufs_qcom_phy_power_on,
+	.power_off	= ufs_qcom_phy_power_off,
+	.owner		= THIS_MODULE,
+};
+
+static struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
+	.calibrate_phy		= ufs_qcom_phy_qmp_20nm_phy_calibrate,
+	.start_serdes		= ufs_qcom_phy_qmp_20nm_start_serdes,
+	.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_20nm_is_pcs_ready,
+	.set_tx_lane_enable	= ufs_qcom_phy_qmp_20nm_set_tx_lane_enable,
+	.power_control		= ufs_qcom_phy_qmp_20nm_power_control,
+};
+
+static int ufs_qcom_phy_qmp_20nm_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct phy *generic_phy;
+	struct ufs_qcom_phy_qmp_20nm *phy;
+	int err = 0;
+
+	phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+	if (!phy) {
+		dev_err(dev, "%s: failed to allocate phy\n", __func__);
+		err = -ENOMEM;
+		goto out;
+	}
+
+	generic_phy = ufs_qcom_phy_generic_probe(pdev, &phy->common_cfg,
+				&ufs_qcom_phy_qmp_20nm_phy_ops, &phy_20nm_ops);
+
+	if (!generic_phy) {
+		dev_err(dev, "%s: ufs_qcom_phy_generic_probe() failed\n",
+			__func__);
+		err = -EIO;
+		goto out;
+	}
+
+	phy_set_drvdata(generic_phy, phy);
+
+	strlcpy(phy->common_cfg.name, UFS_PHY_NAME,
+			sizeof(phy->common_cfg.name));
+
+out:
+	return err;
+}
+
+static int ufs_qcom_phy_qmp_20nm_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct phy *generic_phy = to_phy(dev);
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+	int err = 0;
+
+	err = ufs_qcom_phy_remove(generic_phy, ufs_qcom_phy);
+	if (err)
+		dev_err(dev, "%s: ufs_qcom_phy_remove failed = %d\n",
+			__func__, err);
+
+	return err;
+}
+
+static const struct of_device_id ufs_qcom_phy_qmp_20nm_of_match[] = {
+	{.compatible = "qcom,ufs-phy-qmp-20nm"},
+	{},
+};
+MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_20nm_of_match);
+
+static struct platform_driver ufs_qcom_phy_qmp_20nm_driver = {
+	.probe = ufs_qcom_phy_qmp_20nm_probe,
+	.remove = ufs_qcom_phy_qmp_20nm_remove,
+	.driver = {
+		.of_match_table = ufs_qcom_phy_qmp_20nm_of_match,
+		.name = "ufs_qcom_phy_qmp_20nm",
+		.owner = THIS_MODULE,
+	},
+};
+
+module_platform_driver(ufs_qcom_phy_qmp_20nm_driver);
+
+MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 20nm");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.h b/drivers/phy/phy-qcom-ufs-qmp-20nm.h
new file mode 100644
index 0000000..4f3076b
--- /dev/null
+++ b/drivers/phy/phy-qcom-ufs-qmp-20nm.h
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2013-2015, 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.
+ *
+ */
+
+#ifndef UFS_QCOM_PHY_QMP_20NM_H_
+#define UFS_QCOM_PHY_QMP_20NM_H_
+
+#include "phy-qcom-ufs-i.h"
+
+/* QCOM UFS PHY control registers */
+
+#define COM_OFF(x)     (0x000 + x)
+#define PHY_OFF(x)     (0xC00 + x)
+#define TX_OFF(n, x)   (0x400 + (0x400 * n) + x)
+#define RX_OFF(n, x)   (0x600 + (0x400 * n) + x)
+
+/* UFS PHY PLL block registers */
+#define QSERDES_COM_SYS_CLK_CTRL		COM_OFF(0x0)
+#define QSERDES_COM_PLL_VCOTAIL_EN		COM_OFF(0x04)
+#define QSERDES_COM_PLL_CNTRL			COM_OFF(0x14)
+#define QSERDES_COM_PLL_IP_SETI			COM_OFF(0x24)
+#define QSERDES_COM_CORE_CLK_IN_SYNC_SEL	COM_OFF(0x28)
+#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN		COM_OFF(0x30)
+#define QSERDES_COM_PLL_CP_SETI			COM_OFF(0x34)
+#define QSERDES_COM_PLL_IP_SETP			COM_OFF(0x38)
+#define QSERDES_COM_PLL_CP_SETP			COM_OFF(0x3C)
+#define QSERDES_COM_SYSCLK_EN_SEL_TXBAND	COM_OFF(0x48)
+#define QSERDES_COM_RESETSM_CNTRL		COM_OFF(0x4C)
+#define QSERDES_COM_RESETSM_CNTRL2		COM_OFF(0x50)
+#define QSERDES_COM_PLLLOCK_CMP1		COM_OFF(0x90)
+#define QSERDES_COM_PLLLOCK_CMP2		COM_OFF(0x94)
+#define QSERDES_COM_PLLLOCK_CMP3		COM_OFF(0x98)
+#define QSERDES_COM_PLLLOCK_CMP_EN		COM_OFF(0x9C)
+#define QSERDES_COM_BGTC			COM_OFF(0xA0)
+#define QSERDES_COM_DEC_START1			COM_OFF(0xAC)
+#define QSERDES_COM_PLL_AMP_OS			COM_OFF(0xB0)
+#define QSERDES_COM_RES_CODE_UP_OFFSET		COM_OFF(0xD8)
+#define QSERDES_COM_RES_CODE_DN_OFFSET		COM_OFF(0xDC)
+#define QSERDES_COM_DIV_FRAC_START1		COM_OFF(0x100)
+#define QSERDES_COM_DIV_FRAC_START2		COM_OFF(0x104)
+#define QSERDES_COM_DIV_FRAC_START3		COM_OFF(0x108)
+#define QSERDES_COM_DEC_START2			COM_OFF(0x10C)
+#define QSERDES_COM_PLL_RXTXEPCLK_EN		COM_OFF(0x110)
+#define QSERDES_COM_PLL_CRCTRL			COM_OFF(0x114)
+#define QSERDES_COM_PLL_CLKEPDIV		COM_OFF(0x118)
+
+/* TX LANE n (0, 1) registers */
+#define QSERDES_TX_EMP_POST1_LVL(n)		TX_OFF(n, 0x08)
+#define QSERDES_TX_DRV_LVL(n)			TX_OFF(n, 0x0C)
+#define QSERDES_TX_LANE_MODE(n)			TX_OFF(n, 0x54)
+
+/* RX LANE n (0, 1) registers */
+#define QSERDES_RX_CDR_CONTROL1(n)		RX_OFF(n, 0x0)
+#define QSERDES_RX_CDR_CONTROL_HALF(n)		RX_OFF(n, 0x8)
+#define QSERDES_RX_RX_EQ_GAIN1_LSB(n)		RX_OFF(n, 0xA8)
+#define QSERDES_RX_RX_EQ_GAIN1_MSB(n)		RX_OFF(n, 0xAC)
+#define QSERDES_RX_RX_EQ_GAIN2_LSB(n)		RX_OFF(n, 0xB0)
+#define QSERDES_RX_RX_EQ_GAIN2_MSB(n)		RX_OFF(n, 0xB4)
+#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(n)	RX_OFF(n, 0xBC)
+#define QSERDES_RX_CDR_CONTROL_QUARTER(n)	RX_OFF(n, 0xC)
+#define QSERDES_RX_SIGDET_CNTRL(n)		RX_OFF(n, 0x100)
+
+/* UFS PHY registers */
+#define UFS_PHY_PHY_START			PHY_OFF(0x00)
+#define UFS_PHY_POWER_DOWN_CONTROL		PHY_OFF(0x4)
+#define UFS_PHY_TX_LANE_ENABLE			PHY_OFF(0x44)
+#define UFS_PHY_PWM_G1_CLK_DIVIDER		PHY_OFF(0x08)
+#define UFS_PHY_PWM_G2_CLK_DIVIDER		PHY_OFF(0x0C)
+#define UFS_PHY_PWM_G3_CLK_DIVIDER		PHY_OFF(0x10)
+#define UFS_PHY_PWM_G4_CLK_DIVIDER		PHY_OFF(0x14)
+#define UFS_PHY_CORECLK_PWM_G1_CLK_DIVIDER	PHY_OFF(0x34)
+#define UFS_PHY_CORECLK_PWM_G2_CLK_DIVIDER	PHY_OFF(0x38)
+#define UFS_PHY_CORECLK_PWM_G3_CLK_DIVIDER	PHY_OFF(0x3C)
+#define UFS_PHY_CORECLK_PWM_G4_CLK_DIVIDER	PHY_OFF(0x40)
+#define UFS_PHY_OMC_STATUS_RDVAL		PHY_OFF(0x68)
+#define UFS_PHY_LINE_RESET_TIME			PHY_OFF(0x28)
+#define UFS_PHY_LINE_RESET_GRANULARITY		PHY_OFF(0x2C)
+#define UFS_PHY_TSYNC_RSYNC_CNTL		PHY_OFF(0x48)
+#define UFS_PHY_PLL_CNTL			PHY_OFF(0x50)
+#define UFS_PHY_TX_LARGE_AMP_DRV_LVL		PHY_OFF(0x54)
+#define UFS_PHY_TX_SMALL_AMP_DRV_LVL		PHY_OFF(0x5C)
+#define UFS_PHY_TX_LARGE_AMP_POST_EMP_LVL	PHY_OFF(0x58)
+#define UFS_PHY_TX_SMALL_AMP_POST_EMP_LVL	PHY_OFF(0x60)
+#define UFS_PHY_CFG_CHANGE_CNT_VAL		PHY_OFF(0x64)
+#define UFS_PHY_RX_SYNC_WAIT_TIME		PHY_OFF(0x6C)
+#define UFS_PHY_TX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xB4)
+#define UFS_PHY_RX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xE0)
+#define UFS_PHY_TX_MIN_STALL_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xB8)
+#define UFS_PHY_RX_MIN_STALL_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xE4)
+#define UFS_PHY_TX_MIN_SAVE_CONFIG_TIME_CAPABILITY	PHY_OFF(0xBC)
+#define UFS_PHY_RX_MIN_SAVE_CONFIG_TIME_CAPABILITY	PHY_OFF(0xE8)
+#define UFS_PHY_RX_PWM_BURST_CLOSURE_LENGTH_CAPABILITY	PHY_OFF(0xFC)
+#define UFS_PHY_RX_MIN_ACTIVATETIME_CAPABILITY		PHY_OFF(0x100)
+#define UFS_PHY_RX_SIGDET_CTRL3				PHY_OFF(0x14c)
+#define UFS_PHY_RMMI_ATTR_CTRL			PHY_OFF(0x160)
+#define UFS_PHY_RMMI_RX_CFGUPDT_L1	(1 << 7)
+#define UFS_PHY_RMMI_TX_CFGUPDT_L1	(1 << 6)
+#define UFS_PHY_RMMI_CFGWR_L1		(1 << 5)
+#define UFS_PHY_RMMI_CFGRD_L1		(1 << 4)
+#define UFS_PHY_RMMI_RX_CFGUPDT_L0	(1 << 3)
+#define UFS_PHY_RMMI_TX_CFGUPDT_L0	(1 << 2)
+#define UFS_PHY_RMMI_CFGWR_L0		(1 << 1)
+#define UFS_PHY_RMMI_CFGRD_L0		(1 << 0)
+#define UFS_PHY_RMMI_ATTRID			PHY_OFF(0x164)
+#define UFS_PHY_RMMI_ATTRWRVAL			PHY_OFF(0x168)
+#define UFS_PHY_RMMI_ATTRRDVAL_L0_STATUS	PHY_OFF(0x16C)
+#define UFS_PHY_RMMI_ATTRRDVAL_L1_STATUS	PHY_OFF(0x170)
+#define UFS_PHY_PCS_READY_STATUS		PHY_OFF(0x174)
+
+#define UFS_PHY_TX_LANE_ENABLE_MASK		0x3
+
+/*
+ * This structure represents the 20nm specific phy.
+ * common_cfg MUST remain the first field in this structure
+ * in case extra fields are added. This way, when calling
+ * get_ufs_qcom_phy() of generic phy, we can extract the
+ * common phy structure (struct ufs_qcom_phy) out of it
+ * regardless of the relevant specific phy.
+ */
+struct ufs_qcom_phy_qmp_20nm {
+	struct ufs_qcom_phy common_cfg;
+};
+
+static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_2_0[] = {
+	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
+	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x3f),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x1b),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x0f),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(0), 0x2F),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(0), 0x20),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(1), 0x2F),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(1), 0x20),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3),
+};
+
+static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_3_0[] = {
+	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
+	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x2b),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x38),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x3c),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_UP_OFFSET, 0x02),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_DN_OFFSET, 0x02),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CNTRL, 0x40),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3),
+};
+
+static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x98),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0x65),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x1e),
+};
+
+#endif
diff --git a/include/linux/phy/phy-qcom-ufs.h b/include/linux/phy/phy-qcom-ufs.h
new file mode 100644
index 0000000..9d18e9f
--- /dev/null
+++ b/include/linux/phy/phy-qcom-ufs.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2013-2015, 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.
+ *
+ */
+
+#ifndef PHY_QCOM_UFS_H_
+#define PHY_QCOM_UFS_H_
+
+#include "phy.h"
+
+/**
+ * ufs_qcom_phy_enable_ref_clk() - Enable the phy
+ * ref clock.
+ * @phy: reference to a generic phy
+ *
+ * returns 0 for success, and non-zero for error.
+ */
+int ufs_qcom_phy_enable_ref_clk(struct phy *phy);
+
+/**
+ * ufs_qcom_phy_disable_ref_clk() - Disable the phy
+ * ref clock.
+ * @phy: reference to a generic phy.
+ */
+void ufs_qcom_phy_disable_ref_clk(struct phy *phy);
+
+/**
+ * ufs_qcom_phy_enable_dev_ref_clk() - Enable the device
+ * ref clock.
+ * @phy: reference to a generic phy.
+ */
+void ufs_qcom_phy_enable_dev_ref_clk(struct phy *phy);
+
+/**
+ * ufs_qcom_phy_disable_dev_ref_clk() - Disable the device
+ * ref clock.
+ * @phy: reference to a generic phy.
+ */
+void ufs_qcom_phy_disable_dev_ref_clk(struct phy *phy);
+
+int ufs_qcom_phy_enable_iface_clk(struct phy *phy);
+void ufs_qcom_phy_disable_iface_clk(struct phy *phy);
+int ufs_qcom_phy_start_serdes(struct phy *phy);
+int ufs_qcom_phy_set_tx_lane_enable(struct phy *phy, u32 tx_lanes);
+int ufs_qcom_phy_calibrate_phy(struct phy *phy, bool is_rate_B);
+int ufs_qcom_phy_is_pcs_ready(struct phy *phy);
+void ufs_qcom_phy_save_controller_version(struct phy *phy,
+			u8 major, u16 minor, u16 step);
+
+#endif /* PHY_QCOM_UFS_H_ */
-- 
1.8.5.2

-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v7 2/5] phy: qcom-ufs: add support for 20nm phy
@ 2015-01-15 14:32   ` Yaniv Gardi
  0 siblings, 0 replies; 27+ messages in thread
From: Yaniv Gardi @ 2015-01-15 14:32 UTC (permalink / raw)
  To: James.Bottomley, hch
  Cc: linux-kernel, linux-scsi, linux-arm-msm, santoshsy,
	linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Kishon Vijay Abraham I, Grant Likely, Rob Herring,
	open list:OPEN FIRMWARE AND...

This change adds a support for a 20nm qcom-ufs phy that is required in
platforms that use ufs-qcom controller.

Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>

---
 drivers/phy/Makefile                |   1 +
 drivers/phy/phy-qcom-ufs-i.h        |  43 +++++-
 drivers/phy/phy-qcom-ufs-qmp-20nm.c | 257 ++++++++++++++++++++++++++++++++++++
 drivers/phy/phy-qcom-ufs-qmp-20nm.h | 235 +++++++++++++++++++++++++++++++++
 include/linux/phy/phy-qcom-ufs.h    |  59 +++++++++
 5 files changed, 594 insertions(+), 1 deletion(-)
 create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.c
 create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.h
 create mode 100644 include/linux/phy/phy-qcom-ufs.h

diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 335965d..781b2fa 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -35,3 +35,4 @@ obj-$(CONFIG_PHY_XGENE)			+= phy-xgene.o
 obj-$(CONFIG_PHY_STIH407_USB)		+= phy-stih407-usb.o
 obj-$(CONFIG_PHY_STIH41X_USB)		+= phy-stih41x-usb.o
 obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs.o
+obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs-qmp-20nm.o
diff --git a/drivers/phy/phy-qcom-ufs-i.h b/drivers/phy/phy-qcom-ufs-i.h
index dac200f..591a391 100644
--- a/drivers/phy/phy-qcom-ufs-i.h
+++ b/drivers/phy/phy-qcom-ufs-i.h
@@ -15,15 +15,56 @@
 #ifndef UFS_QCOM_PHY_I_H_
 #define UFS_QCOM_PHY_I_H_
 
+#include <linux/module.h>
 #include <linux/clk.h>
+#include <linux/regulator/consumer.h>
 #include <linux/slab.h>
-#include <linux/phy/phy.h>
+#include <linux/phy/phy-qcom-ufs.h>
 #include <linux/platform_device.h>
 #include <linux/io.h>
 #include <linux/delay.h>
 
+#define readl_poll_timeout(addr, val, cond, sleep_us, timeout_us) \
+({ \
+	ktime_t timeout = ktime_add_us(ktime_get(), timeout_us); \
+	might_sleep_if(timeout_us); \
+	for (;;) { \
+		(val) = readl(addr); \
+		if (cond) \
+			break; \
+		if (timeout_us && ktime_compare(ktime_get(), timeout) > 0) { \
+			(val) = readl(addr); \
+			break; \
+		} \
+		if (sleep_us) \
+			usleep_range(DIV_ROUND_UP(sleep_us, 4), sleep_us); \
+	} \
+	(cond) ? 0 : -ETIMEDOUT; \
+})
+
+#define UFS_QCOM_PHY_CAL_ENTRY(reg, val)	\
+	{				\
+		.reg_offset = reg,	\
+		.cfg_value = val,	\
+	}
+
 #define UFS_QCOM_PHY_NAME_LEN	30
 
+enum {
+	MASK_SERDES_START       = 0x1,
+	MASK_PCS_READY          = 0x1,
+};
+
+enum {
+	OFFSET_SERDES_START     = 0x0,
+};
+
+struct ufs_qcom_phy_stored_attributes {
+	u32 att;
+	u32 value;
+};
+
+
 struct ufs_qcom_phy_calibration {
 	u32 reg_offset;
 	u32 cfg_value;
diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.c b/drivers/phy/phy-qcom-ufs-qmp-20nm.c
new file mode 100644
index 0000000..8332f96
--- /dev/null
+++ b/drivers/phy/phy-qcom-ufs-qmp-20nm.c
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2013-2015, 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 "phy-qcom-ufs-qmp-20nm.h"
+
+#define UFS_PHY_NAME "ufs_phy_qmp_20nm"
+
+static
+int ufs_qcom_phy_qmp_20nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
+					bool is_rate_B)
+{
+	struct ufs_qcom_phy_calibration *tbl_A, *tbl_B;
+	int tbl_size_A, tbl_size_B;
+	u8 major = ufs_qcom_phy->host_ctrl_rev_major;
+	u16 minor = ufs_qcom_phy->host_ctrl_rev_minor;
+	u16 step = ufs_qcom_phy->host_ctrl_rev_step;
+	int err;
+
+	if ((major == 0x1) && (minor == 0x002) && (step == 0x0000)) {
+		tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_2_0);
+		tbl_A = phy_cal_table_rate_A_1_2_0;
+	} else if ((major == 0x1) && (minor == 0x003) && (step == 0x0000)) {
+		tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_3_0);
+		tbl_A = phy_cal_table_rate_A_1_3_0;
+	} else {
+		dev_err(ufs_qcom_phy->dev, "%s: Unknown UFS-PHY version, no calibration values\n",
+			__func__);
+		err = -ENODEV;
+		goto out;
+	}
+
+	tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
+	tbl_B = phy_cal_table_rate_B;
+
+	err = ufs_qcom_phy_calibrate(ufs_qcom_phy, tbl_A, tbl_size_A,
+						tbl_B, tbl_size_B, is_rate_B);
+
+	if (err)
+		dev_err(ufs_qcom_phy->dev, "%s: ufs_qcom_phy_calibrate() failed %d\n",
+			__func__, err);
+
+out:
+	return err;
+}
+
+static
+void ufs_qcom_phy_qmp_20nm_advertise_quirks(struct ufs_qcom_phy *phy_common)
+{
+	phy_common->quirks =
+		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
+}
+
+static int ufs_qcom_phy_qmp_20nm_init(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy_qmp_20nm *phy = phy_get_drvdata(generic_phy);
+	struct ufs_qcom_phy *phy_common = &phy->common_cfg;
+	int err = 0;
+
+	err = ufs_qcom_phy_init_clks(generic_phy, phy_common);
+	if (err) {
+		dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_clks() failed %d\n",
+			__func__, err);
+		goto out;
+	}
+
+	err = ufs_qcom_phy_init_vregulators(generic_phy, phy_common);
+	if (err) {
+		dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_vregulators() failed %d\n",
+			__func__, err);
+		goto out;
+	}
+
+	ufs_qcom_phy_qmp_20nm_advertise_quirks(phy_common);
+
+out:
+	return err;
+}
+
+static
+void ufs_qcom_phy_qmp_20nm_power_control(struct ufs_qcom_phy *phy, bool val)
+{
+	bool hibern8_exit_after_pwr_collapse = phy->quirks &
+		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
+
+	if (val) {
+		writel_relaxed(0x1, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
+		/*
+		 * Before any transactions involving PHY, ensure PHY knows
+		 * that it's analog rail is powered ON.
+		 */
+		mb();
+
+		if (hibern8_exit_after_pwr_collapse) {
+			/*
+			 * Give atleast 1us delay after restoring PHY analog
+			 * power.
+			 */
+			usleep_range(1, 2);
+			writel_relaxed(0x0A, phy->mmio +
+				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
+			writel_relaxed(0x08, phy->mmio +
+				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
+			/*
+			 * Make sure workaround is deactivated before proceeding
+			 * with normal PHY operations.
+			 */
+			mb();
+		}
+	} else {
+		if (hibern8_exit_after_pwr_collapse) {
+			writel_relaxed(0x0A, phy->mmio +
+				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
+			writel_relaxed(0x02, phy->mmio +
+				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
+			/*
+			 * Make sure that above workaround is activated before
+			 * PHY analog power collapse.
+			 */
+			mb();
+		}
+
+		writel_relaxed(0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
+		/*
+		 * ensure that PHY knows its PHY analog rail is going
+		 * to be powered down
+		 */
+		mb();
+	}
+}
+
+static
+void ufs_qcom_phy_qmp_20nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val)
+{
+	writel_relaxed(val & UFS_PHY_TX_LANE_ENABLE_MASK,
+			phy->mmio + UFS_PHY_TX_LANE_ENABLE);
+	mb();
+}
+
+static inline void ufs_qcom_phy_qmp_20nm_start_serdes(struct ufs_qcom_phy *phy)
+{
+	u32 tmp;
+
+	tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
+	tmp &= ~MASK_SERDES_START;
+	tmp |= (1 << OFFSET_SERDES_START);
+	writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
+	mb();
+}
+
+static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
+{
+	int err = 0;
+	u32 val;
+
+	err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS,
+			val, (val & MASK_PCS_READY), 10, 1000000);
+	if (err)
+		dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n",
+			__func__, err);
+	return err;
+}
+
+static struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
+	.init		= ufs_qcom_phy_qmp_20nm_init,
+	.exit		= ufs_qcom_phy_exit,
+	.power_on	= ufs_qcom_phy_power_on,
+	.power_off	= ufs_qcom_phy_power_off,
+	.owner		= THIS_MODULE,
+};
+
+static struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
+	.calibrate_phy		= ufs_qcom_phy_qmp_20nm_phy_calibrate,
+	.start_serdes		= ufs_qcom_phy_qmp_20nm_start_serdes,
+	.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_20nm_is_pcs_ready,
+	.set_tx_lane_enable	= ufs_qcom_phy_qmp_20nm_set_tx_lane_enable,
+	.power_control		= ufs_qcom_phy_qmp_20nm_power_control,
+};
+
+static int ufs_qcom_phy_qmp_20nm_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct phy *generic_phy;
+	struct ufs_qcom_phy_qmp_20nm *phy;
+	int err = 0;
+
+	phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+	if (!phy) {
+		dev_err(dev, "%s: failed to allocate phy\n", __func__);
+		err = -ENOMEM;
+		goto out;
+	}
+
+	generic_phy = ufs_qcom_phy_generic_probe(pdev, &phy->common_cfg,
+				&ufs_qcom_phy_qmp_20nm_phy_ops, &phy_20nm_ops);
+
+	if (!generic_phy) {
+		dev_err(dev, "%s: ufs_qcom_phy_generic_probe() failed\n",
+			__func__);
+		err = -EIO;
+		goto out;
+	}
+
+	phy_set_drvdata(generic_phy, phy);
+
+	strlcpy(phy->common_cfg.name, UFS_PHY_NAME,
+			sizeof(phy->common_cfg.name));
+
+out:
+	return err;
+}
+
+static int ufs_qcom_phy_qmp_20nm_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct phy *generic_phy = to_phy(dev);
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+	int err = 0;
+
+	err = ufs_qcom_phy_remove(generic_phy, ufs_qcom_phy);
+	if (err)
+		dev_err(dev, "%s: ufs_qcom_phy_remove failed = %d\n",
+			__func__, err);
+
+	return err;
+}
+
+static const struct of_device_id ufs_qcom_phy_qmp_20nm_of_match[] = {
+	{.compatible = "qcom,ufs-phy-qmp-20nm"},
+	{},
+};
+MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_20nm_of_match);
+
+static struct platform_driver ufs_qcom_phy_qmp_20nm_driver = {
+	.probe = ufs_qcom_phy_qmp_20nm_probe,
+	.remove = ufs_qcom_phy_qmp_20nm_remove,
+	.driver = {
+		.of_match_table = ufs_qcom_phy_qmp_20nm_of_match,
+		.name = "ufs_qcom_phy_qmp_20nm",
+		.owner = THIS_MODULE,
+	},
+};
+
+module_platform_driver(ufs_qcom_phy_qmp_20nm_driver);
+
+MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 20nm");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.h b/drivers/phy/phy-qcom-ufs-qmp-20nm.h
new file mode 100644
index 0000000..4f3076b
--- /dev/null
+++ b/drivers/phy/phy-qcom-ufs-qmp-20nm.h
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2013-2015, 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.
+ *
+ */
+
+#ifndef UFS_QCOM_PHY_QMP_20NM_H_
+#define UFS_QCOM_PHY_QMP_20NM_H_
+
+#include "phy-qcom-ufs-i.h"
+
+/* QCOM UFS PHY control registers */
+
+#define COM_OFF(x)     (0x000 + x)
+#define PHY_OFF(x)     (0xC00 + x)
+#define TX_OFF(n, x)   (0x400 + (0x400 * n) + x)
+#define RX_OFF(n, x)   (0x600 + (0x400 * n) + x)
+
+/* UFS PHY PLL block registers */
+#define QSERDES_COM_SYS_CLK_CTRL		COM_OFF(0x0)
+#define QSERDES_COM_PLL_VCOTAIL_EN		COM_OFF(0x04)
+#define QSERDES_COM_PLL_CNTRL			COM_OFF(0x14)
+#define QSERDES_COM_PLL_IP_SETI			COM_OFF(0x24)
+#define QSERDES_COM_CORE_CLK_IN_SYNC_SEL	COM_OFF(0x28)
+#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN		COM_OFF(0x30)
+#define QSERDES_COM_PLL_CP_SETI			COM_OFF(0x34)
+#define QSERDES_COM_PLL_IP_SETP			COM_OFF(0x38)
+#define QSERDES_COM_PLL_CP_SETP			COM_OFF(0x3C)
+#define QSERDES_COM_SYSCLK_EN_SEL_TXBAND	COM_OFF(0x48)
+#define QSERDES_COM_RESETSM_CNTRL		COM_OFF(0x4C)
+#define QSERDES_COM_RESETSM_CNTRL2		COM_OFF(0x50)
+#define QSERDES_COM_PLLLOCK_CMP1		COM_OFF(0x90)
+#define QSERDES_COM_PLLLOCK_CMP2		COM_OFF(0x94)
+#define QSERDES_COM_PLLLOCK_CMP3		COM_OFF(0x98)
+#define QSERDES_COM_PLLLOCK_CMP_EN		COM_OFF(0x9C)
+#define QSERDES_COM_BGTC			COM_OFF(0xA0)
+#define QSERDES_COM_DEC_START1			COM_OFF(0xAC)
+#define QSERDES_COM_PLL_AMP_OS			COM_OFF(0xB0)
+#define QSERDES_COM_RES_CODE_UP_OFFSET		COM_OFF(0xD8)
+#define QSERDES_COM_RES_CODE_DN_OFFSET		COM_OFF(0xDC)
+#define QSERDES_COM_DIV_FRAC_START1		COM_OFF(0x100)
+#define QSERDES_COM_DIV_FRAC_START2		COM_OFF(0x104)
+#define QSERDES_COM_DIV_FRAC_START3		COM_OFF(0x108)
+#define QSERDES_COM_DEC_START2			COM_OFF(0x10C)
+#define QSERDES_COM_PLL_RXTXEPCLK_EN		COM_OFF(0x110)
+#define QSERDES_COM_PLL_CRCTRL			COM_OFF(0x114)
+#define QSERDES_COM_PLL_CLKEPDIV		COM_OFF(0x118)
+
+/* TX LANE n (0, 1) registers */
+#define QSERDES_TX_EMP_POST1_LVL(n)		TX_OFF(n, 0x08)
+#define QSERDES_TX_DRV_LVL(n)			TX_OFF(n, 0x0C)
+#define QSERDES_TX_LANE_MODE(n)			TX_OFF(n, 0x54)
+
+/* RX LANE n (0, 1) registers */
+#define QSERDES_RX_CDR_CONTROL1(n)		RX_OFF(n, 0x0)
+#define QSERDES_RX_CDR_CONTROL_HALF(n)		RX_OFF(n, 0x8)
+#define QSERDES_RX_RX_EQ_GAIN1_LSB(n)		RX_OFF(n, 0xA8)
+#define QSERDES_RX_RX_EQ_GAIN1_MSB(n)		RX_OFF(n, 0xAC)
+#define QSERDES_RX_RX_EQ_GAIN2_LSB(n)		RX_OFF(n, 0xB0)
+#define QSERDES_RX_RX_EQ_GAIN2_MSB(n)		RX_OFF(n, 0xB4)
+#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(n)	RX_OFF(n, 0xBC)
+#define QSERDES_RX_CDR_CONTROL_QUARTER(n)	RX_OFF(n, 0xC)
+#define QSERDES_RX_SIGDET_CNTRL(n)		RX_OFF(n, 0x100)
+
+/* UFS PHY registers */
+#define UFS_PHY_PHY_START			PHY_OFF(0x00)
+#define UFS_PHY_POWER_DOWN_CONTROL		PHY_OFF(0x4)
+#define UFS_PHY_TX_LANE_ENABLE			PHY_OFF(0x44)
+#define UFS_PHY_PWM_G1_CLK_DIVIDER		PHY_OFF(0x08)
+#define UFS_PHY_PWM_G2_CLK_DIVIDER		PHY_OFF(0x0C)
+#define UFS_PHY_PWM_G3_CLK_DIVIDER		PHY_OFF(0x10)
+#define UFS_PHY_PWM_G4_CLK_DIVIDER		PHY_OFF(0x14)
+#define UFS_PHY_CORECLK_PWM_G1_CLK_DIVIDER	PHY_OFF(0x34)
+#define UFS_PHY_CORECLK_PWM_G2_CLK_DIVIDER	PHY_OFF(0x38)
+#define UFS_PHY_CORECLK_PWM_G3_CLK_DIVIDER	PHY_OFF(0x3C)
+#define UFS_PHY_CORECLK_PWM_G4_CLK_DIVIDER	PHY_OFF(0x40)
+#define UFS_PHY_OMC_STATUS_RDVAL		PHY_OFF(0x68)
+#define UFS_PHY_LINE_RESET_TIME			PHY_OFF(0x28)
+#define UFS_PHY_LINE_RESET_GRANULARITY		PHY_OFF(0x2C)
+#define UFS_PHY_TSYNC_RSYNC_CNTL		PHY_OFF(0x48)
+#define UFS_PHY_PLL_CNTL			PHY_OFF(0x50)
+#define UFS_PHY_TX_LARGE_AMP_DRV_LVL		PHY_OFF(0x54)
+#define UFS_PHY_TX_SMALL_AMP_DRV_LVL		PHY_OFF(0x5C)
+#define UFS_PHY_TX_LARGE_AMP_POST_EMP_LVL	PHY_OFF(0x58)
+#define UFS_PHY_TX_SMALL_AMP_POST_EMP_LVL	PHY_OFF(0x60)
+#define UFS_PHY_CFG_CHANGE_CNT_VAL		PHY_OFF(0x64)
+#define UFS_PHY_RX_SYNC_WAIT_TIME		PHY_OFF(0x6C)
+#define UFS_PHY_TX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xB4)
+#define UFS_PHY_RX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xE0)
+#define UFS_PHY_TX_MIN_STALL_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xB8)
+#define UFS_PHY_RX_MIN_STALL_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xE4)
+#define UFS_PHY_TX_MIN_SAVE_CONFIG_TIME_CAPABILITY	PHY_OFF(0xBC)
+#define UFS_PHY_RX_MIN_SAVE_CONFIG_TIME_CAPABILITY	PHY_OFF(0xE8)
+#define UFS_PHY_RX_PWM_BURST_CLOSURE_LENGTH_CAPABILITY	PHY_OFF(0xFC)
+#define UFS_PHY_RX_MIN_ACTIVATETIME_CAPABILITY		PHY_OFF(0x100)
+#define UFS_PHY_RX_SIGDET_CTRL3				PHY_OFF(0x14c)
+#define UFS_PHY_RMMI_ATTR_CTRL			PHY_OFF(0x160)
+#define UFS_PHY_RMMI_RX_CFGUPDT_L1	(1 << 7)
+#define UFS_PHY_RMMI_TX_CFGUPDT_L1	(1 << 6)
+#define UFS_PHY_RMMI_CFGWR_L1		(1 << 5)
+#define UFS_PHY_RMMI_CFGRD_L1		(1 << 4)
+#define UFS_PHY_RMMI_RX_CFGUPDT_L0	(1 << 3)
+#define UFS_PHY_RMMI_TX_CFGUPDT_L0	(1 << 2)
+#define UFS_PHY_RMMI_CFGWR_L0		(1 << 1)
+#define UFS_PHY_RMMI_CFGRD_L0		(1 << 0)
+#define UFS_PHY_RMMI_ATTRID			PHY_OFF(0x164)
+#define UFS_PHY_RMMI_ATTRWRVAL			PHY_OFF(0x168)
+#define UFS_PHY_RMMI_ATTRRDVAL_L0_STATUS	PHY_OFF(0x16C)
+#define UFS_PHY_RMMI_ATTRRDVAL_L1_STATUS	PHY_OFF(0x170)
+#define UFS_PHY_PCS_READY_STATUS		PHY_OFF(0x174)
+
+#define UFS_PHY_TX_LANE_ENABLE_MASK		0x3
+
+/*
+ * This structure represents the 20nm specific phy.
+ * common_cfg MUST remain the first field in this structure
+ * in case extra fields are added. This way, when calling
+ * get_ufs_qcom_phy() of generic phy, we can extract the
+ * common phy structure (struct ufs_qcom_phy) out of it
+ * regardless of the relevant specific phy.
+ */
+struct ufs_qcom_phy_qmp_20nm {
+	struct ufs_qcom_phy common_cfg;
+};
+
+static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_2_0[] = {
+	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
+	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x3f),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x1b),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x0f),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(0), 0x2F),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(0), 0x20),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(1), 0x2F),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(1), 0x20),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3),
+};
+
+static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_3_0[] = {
+	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
+	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x2b),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x38),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x3c),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_UP_OFFSET, 0x02),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_DN_OFFSET, 0x02),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CNTRL, 0x40),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3),
+};
+
+static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x98),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0x65),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x1e),
+};
+
+#endif
diff --git a/include/linux/phy/phy-qcom-ufs.h b/include/linux/phy/phy-qcom-ufs.h
new file mode 100644
index 0000000..9d18e9f
--- /dev/null
+++ b/include/linux/phy/phy-qcom-ufs.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2013-2015, 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.
+ *
+ */
+
+#ifndef PHY_QCOM_UFS_H_
+#define PHY_QCOM_UFS_H_
+
+#include "phy.h"
+
+/**
+ * ufs_qcom_phy_enable_ref_clk() - Enable the phy
+ * ref clock.
+ * @phy: reference to a generic phy
+ *
+ * returns 0 for success, and non-zero for error.
+ */
+int ufs_qcom_phy_enable_ref_clk(struct phy *phy);
+
+/**
+ * ufs_qcom_phy_disable_ref_clk() - Disable the phy
+ * ref clock.
+ * @phy: reference to a generic phy.
+ */
+void ufs_qcom_phy_disable_ref_clk(struct phy *phy);
+
+/**
+ * ufs_qcom_phy_enable_dev_ref_clk() - Enable the device
+ * ref clock.
+ * @phy: reference to a generic phy.
+ */
+void ufs_qcom_phy_enable_dev_ref_clk(struct phy *phy);
+
+/**
+ * ufs_qcom_phy_disable_dev_ref_clk() - Disable the device
+ * ref clock.
+ * @phy: reference to a generic phy.
+ */
+void ufs_qcom_phy_disable_dev_ref_clk(struct phy *phy);
+
+int ufs_qcom_phy_enable_iface_clk(struct phy *phy);
+void ufs_qcom_phy_disable_iface_clk(struct phy *phy);
+int ufs_qcom_phy_start_serdes(struct phy *phy);
+int ufs_qcom_phy_set_tx_lane_enable(struct phy *phy, u32 tx_lanes);
+int ufs_qcom_phy_calibrate_phy(struct phy *phy, bool is_rate_B);
+int ufs_qcom_phy_is_pcs_ready(struct phy *phy);
+void ufs_qcom_phy_save_controller_version(struct phy *phy,
+			u8 major, u16 minor, u16 step);
+
+#endif /* PHY_QCOM_UFS_H_ */
-- 
1.8.5.2

-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v7 3/5] scsi: ufs-qcom: add support for Qualcomm Technologies Inc platforms
  2015-01-15 14:32 [PATCH v7 0/5] phy: qcom-ufs: add support for QUALCOMM Yaniv Gardi
  2015-01-15 14:32   ` Yaniv Gardi
  2015-01-15 14:32   ` Yaniv Gardi
@ 2015-01-15 14:32 ` Yaniv Gardi
  2015-01-15 15:20     ` Dov Levenglick
  2015-01-15 14:32   ` Yaniv Gardi
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 27+ messages in thread
From: Yaniv Gardi @ 2015-01-15 14:32 UTC (permalink / raw)
  To: James.Bottomley, hch
  Cc: linux-kernel, linux-scsi, linux-arm-msm, santoshsy,
	linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Vinayak Holikatti, James E.J. Bottomley

This change adds support for Qualcomm Technologies Inc platforms that
use UFS driver. for example, it adds :
- PM specific operations during hibern8, suspend, resume, clock setup
- qcom-ufs generic phy driver initialization, calibration,
  power-on/off sequence, etc.
- UFS Controller specific configuration
- Rate, Gear, Mode negotiation between device and controller

Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>

---
 drivers/scsi/ufs/Kconfig    |   13 +
 drivers/scsi/ufs/Makefile   |    1 +
 drivers/scsi/ufs/ufs-qcom.c | 1004 +++++++++++++++++++++++++++++++++++++++++++
 drivers/scsi/ufs/ufs-qcom.h |  170 ++++++++
 4 files changed, 1188 insertions(+)
 create mode 100644 drivers/scsi/ufs/ufs-qcom.c
 create mode 100644 drivers/scsi/ufs/ufs-qcom.h

diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig
index 6e07b2a..8a1f4b3 100644
--- a/drivers/scsi/ufs/Kconfig
+++ b/drivers/scsi/ufs/Kconfig
@@ -70,3 +70,16 @@ config SCSI_UFSHCD_PLATFORM
 	If you have a controller with this interface, say Y or M here.
 
 	  If unsure, say N.
+
+config SCSI_UFS_QCOM
+	bool "QCOM specific hooks to UFS controller platform driver"
+	depends on SCSI_UFSHCD_PLATFORM && ARCH_MSM
+	select PHY_QCOM_UFS
+	help
+	  This selects the QCOM specific additions to UFSHCD platform driver.
+	  UFS host on QCOM needs some vendor specific configuration before
+	  accessing the hardware which includes PHY configuration and vendor
+	  specific registers.
+
+	  Select this if you have UFS controller on QCOM chipset.
+	  If unsure, say N.
diff --git a/drivers/scsi/ufs/Makefile b/drivers/scsi/ufs/Makefile
index 1e5bd48..8303bcc 100644
--- a/drivers/scsi/ufs/Makefile
+++ b/drivers/scsi/ufs/Makefile
@@ -1,4 +1,5 @@
 # UFSHCD makefile
+obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-qcom.o
 obj-$(CONFIG_SCSI_UFSHCD) += ufshcd.o
 obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o
 obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o
diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c
new file mode 100644
index 0000000..9217af9
--- /dev/null
+++ b/drivers/scsi/ufs/ufs-qcom.c
@@ -0,0 +1,1004 @@
+/*
+ * Copyright (c) 2013-2015, 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/time.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+
+#include <linux/phy/phy-qcom-ufs.h>
+#include "ufshcd.h"
+#include "unipro.h"
+#include "ufs-qcom.h"
+#include "ufshci.h"
+
+static struct ufs_qcom_host *ufs_qcom_hosts[MAX_UFS_QCOM_HOSTS];
+
+static void ufs_qcom_get_speed_mode(struct ufs_pa_layer_attr *p, char *result);
+static int ufs_qcom_get_bus_vote(struct ufs_qcom_host *host,
+		const char *speed_mode);
+static int ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote);
+
+static int ufs_qcom_get_connected_tx_lanes(struct ufs_hba *hba, u32 *tx_lanes)
+{
+	int err = 0;
+
+	err = ufshcd_dme_get(hba,
+			UIC_ARG_MIB(PA_CONNECTEDTXDATALANES), tx_lanes);
+	if (err)
+		dev_err(hba->dev, "%s: couldn't read PA_CONNECTEDTXDATALANES %d\n",
+				__func__, err);
+
+	return err;
+}
+
+static int ufs_qcom_host_clk_get(struct device *dev,
+		const char *name, struct clk **clk_out)
+{
+	struct clk *clk;
+	int err = 0;
+
+	clk = devm_clk_get(dev, name);
+	if (IS_ERR(clk)) {
+		err = PTR_ERR(clk);
+		dev_err(dev, "%s: failed to get %s err %d",
+				__func__, name, err);
+	} else {
+		*clk_out = clk;
+	}
+
+	return err;
+}
+
+static int ufs_qcom_host_clk_enable(struct device *dev,
+		const char *name, struct clk *clk)
+{
+	int err = 0;
+
+	err = clk_prepare_enable(clk);
+	if (err)
+		dev_err(dev, "%s: %s enable failed %d\n", __func__, name, err);
+
+	return err;
+}
+
+static void ufs_qcom_disable_lane_clks(struct ufs_qcom_host *host)
+{
+	if (!host->is_lane_clks_enabled)
+		return;
+
+	clk_disable_unprepare(host->tx_l1_sync_clk);
+	clk_disable_unprepare(host->tx_l0_sync_clk);
+	clk_disable_unprepare(host->rx_l1_sync_clk);
+	clk_disable_unprepare(host->rx_l0_sync_clk);
+
+	host->is_lane_clks_enabled = false;
+}
+
+static int ufs_qcom_enable_lane_clks(struct ufs_qcom_host *host)
+{
+	int err = 0;
+	struct device *dev = host->hba->dev;
+
+	if (host->is_lane_clks_enabled)
+		return 0;
+
+	err = ufs_qcom_host_clk_enable(dev, "rx_lane0_sync_clk",
+		host->rx_l0_sync_clk);
+	if (err)
+		goto out;
+
+	err = ufs_qcom_host_clk_enable(dev, "tx_lane0_sync_clk",
+		host->tx_l0_sync_clk);
+	if (err)
+		goto disable_rx_l0;
+
+	err = ufs_qcom_host_clk_enable(dev, "rx_lane1_sync_clk",
+		host->rx_l1_sync_clk);
+	if (err)
+		goto disable_tx_l0;
+
+	err = ufs_qcom_host_clk_enable(dev, "tx_lane1_sync_clk",
+		host->tx_l1_sync_clk);
+	if (err)
+		goto disable_rx_l1;
+
+	host->is_lane_clks_enabled = true;
+	goto out;
+
+disable_rx_l1:
+	clk_disable_unprepare(host->rx_l1_sync_clk);
+disable_tx_l0:
+	clk_disable_unprepare(host->tx_l0_sync_clk);
+disable_rx_l0:
+	clk_disable_unprepare(host->rx_l0_sync_clk);
+out:
+	return err;
+}
+
+static int ufs_qcom_init_lane_clks(struct ufs_qcom_host *host)
+{
+	int err = 0;
+	struct device *dev = host->hba->dev;
+
+	err = ufs_qcom_host_clk_get(dev,
+			"rx_lane0_sync_clk", &host->rx_l0_sync_clk);
+	if (err)
+		goto out;
+
+	err = ufs_qcom_host_clk_get(dev,
+			"tx_lane0_sync_clk", &host->tx_l0_sync_clk);
+	if (err)
+		goto out;
+
+	err = ufs_qcom_host_clk_get(dev, "rx_lane1_sync_clk",
+		&host->rx_l1_sync_clk);
+	if (err)
+		goto out;
+
+	err = ufs_qcom_host_clk_get(dev, "tx_lane1_sync_clk",
+		&host->tx_l1_sync_clk);
+out:
+	return err;
+}
+
+static int ufs_qcom_link_startup_post_change(struct ufs_hba *hba)
+{
+	struct ufs_qcom_host *host = hba->priv;
+	struct phy *phy = host->generic_phy;
+	u32 tx_lanes;
+	int err = 0;
+
+	err = ufs_qcom_get_connected_tx_lanes(hba, &tx_lanes);
+	if (err)
+		goto out;
+
+	err = ufs_qcom_phy_set_tx_lane_enable(phy, tx_lanes);
+	if (err)
+		dev_err(hba->dev, "%s: ufs_qcom_phy_set_tx_lane_enable failed\n",
+			__func__);
+
+out:
+	return err;
+}
+
+static int ufs_qcom_check_hibern8(struct ufs_hba *hba)
+{
+	int err;
+	u32 tx_fsm_val = 0;
+	unsigned long timeout = jiffies + msecs_to_jiffies(HBRN8_POLL_TOUT_MS);
+
+	do {
+		err = ufshcd_dme_get(hba,
+			UIC_ARG_MIB(MPHY_TX_FSM_STATE), &tx_fsm_val);
+		if (err || tx_fsm_val == TX_FSM_HIBERN8)
+			break;
+
+		/* sleep for max. 200us */
+		usleep_range(100, 200);
+	} while (time_before(jiffies, timeout));
+
+	/*
+	 * we might have scheduled out for long during polling so
+	 * check the state again.
+	 */
+	if (time_after(jiffies, timeout))
+		err = ufshcd_dme_get(hba,
+				UIC_ARG_MIB(MPHY_TX_FSM_STATE), &tx_fsm_val);
+
+	if (err) {
+		dev_err(hba->dev, "%s: unable to get TX_FSM_STATE, err %d\n",
+				__func__, err);
+	} else if (tx_fsm_val != TX_FSM_HIBERN8) {
+		err = tx_fsm_val;
+		dev_err(hba->dev, "%s: invalid TX_FSM_STATE = %d\n",
+				__func__, err);
+	}
+
+	return err;
+}
+
+static int ufs_qcom_power_up_sequence(struct ufs_hba *hba)
+{
+	struct ufs_qcom_host *host = hba->priv;
+	struct phy *phy = host->generic_phy;
+	int ret = 0;
+	u8 major;
+	u16 minor, step;
+	bool is_rate_B = (UFS_QCOM_LIMIT_HS_RATE == PA_HS_MODE_B)
+							? true : false;
+
+	/* Assert PHY reset and apply PHY calibration values */
+	ufs_qcom_assert_reset(hba);
+	/* provide 1ms delay to let the reset pulse propagate */
+	usleep_range(1000, 1100);
+
+	ufs_qcom_get_controller_revision(hba, &major, &minor, &step);
+	ufs_qcom_phy_save_controller_version(phy, major, minor, step);
+	ret = ufs_qcom_phy_calibrate_phy(phy, is_rate_B);
+	if (ret) {
+		dev_err(hba->dev, "%s: ufs_qcom_phy_calibrate_phy() failed, ret = %d\n",
+			__func__, ret);
+		goto out;
+	}
+
+	/* De-assert PHY reset and start serdes */
+	ufs_qcom_deassert_reset(hba);
+
+	/*
+	 * after reset deassertion, phy will need all ref clocks,
+	 * voltage, current to settle down before starting serdes.
+	 */
+	usleep_range(1000, 1100);
+	ret = ufs_qcom_phy_start_serdes(phy);
+	if (ret) {
+		dev_err(hba->dev, "%s: ufs_qcom_phy_start_serdes() failed, ret = %d\n",
+			__func__, ret);
+		goto out;
+	}
+
+	ret = ufs_qcom_phy_is_pcs_ready(phy);
+	if (ret)
+		dev_err(hba->dev, "%s: is_physical_coding_sublayer_ready() failed, ret = %d\n",
+			__func__, ret);
+
+out:
+	return ret;
+}
+
+/*
+ * The UTP controller has a number of internal clock gating cells (CGCs).
+ * Internal hardware sub-modules within the UTP controller control the CGCs.
+ * Hardware CGCs disable the clock to inactivate UTP sub-modules not involved
+ * in a specific operation, UTP controller CGCs are by default disabled and
+ * this function enables them (after every UFS link startup) to save some power
+ * leakage.
+ */
+static void ufs_qcom_enable_hw_clk_gating(struct ufs_hba *hba)
+{
+	ufshcd_writel(hba,
+		ufshcd_readl(hba, REG_UFS_CFG2) | REG_UFS_CFG2_CGC_EN_ALL,
+		REG_UFS_CFG2);
+
+	/* Ensure that HW clock gating is enabled before next operations */
+	mb();
+}
+
+static int ufs_qcom_hce_enable_notify(struct ufs_hba *hba, bool status)
+{
+	struct ufs_qcom_host *host = hba->priv;
+	int err = 0;
+
+	switch (status) {
+	case PRE_CHANGE:
+		ufs_qcom_power_up_sequence(hba);
+		/*
+		 * The PHY PLL output is the source of tx/rx lane symbol
+		 * clocks, hence, enable the lane clocks only after PHY
+		 * is initialized.
+		 */
+		err = ufs_qcom_enable_lane_clks(host);
+		break;
+	case POST_CHANGE:
+		/* check if UFS PHY moved from DISABLED to HIBERN8 */
+		err = ufs_qcom_check_hibern8(hba);
+		ufs_qcom_enable_hw_clk_gating(hba);
+
+		break;
+	default:
+		dev_err(hba->dev, "%s: invalid status %d\n", __func__, status);
+		err = -EINVAL;
+		break;
+	}
+	return err;
+}
+
+/**
+ * Returns non-zero for success (which rate of core_clk) and 0
+ * in case of a failure
+ */
+static unsigned long
+ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear, u32 hs, u32 rate)
+{
+	struct ufs_clk_info *clki;
+	u32 core_clk_period_in_ns;
+	u32 tx_clk_cycles_per_us = 0;
+	unsigned long core_clk_rate = 0;
+	u32 core_clk_cycles_per_us = 0;
+
+	static u32 pwm_fr_table[][2] = {
+		{UFS_PWM_G1, 0x1},
+		{UFS_PWM_G2, 0x1},
+		{UFS_PWM_G3, 0x1},
+		{UFS_PWM_G4, 0x1},
+	};
+
+	static u32 hs_fr_table_rA[][2] = {
+		{UFS_HS_G1, 0x1F},
+		{UFS_HS_G2, 0x3e},
+	};
+
+	static u32 hs_fr_table_rB[][2] = {
+		{UFS_HS_G1, 0x24},
+		{UFS_HS_G2, 0x49},
+	};
+
+	if (gear == 0) {
+		dev_err(hba->dev, "%s: invalid gear = %d\n", __func__, gear);
+		goto out_error;
+	}
+
+	list_for_each_entry(clki, &hba->clk_list_head, list) {
+		if (!strcmp(clki->name, "core_clk"))
+			core_clk_rate = clk_get_rate(clki->clk);
+	}
+
+	/* If frequency is smaller than 1MHz, set to 1MHz */
+	if (core_clk_rate < DEFAULT_CLK_RATE_HZ)
+		core_clk_rate = DEFAULT_CLK_RATE_HZ;
+
+	core_clk_cycles_per_us = core_clk_rate / USEC_PER_SEC;
+	ufshcd_writel(hba, core_clk_cycles_per_us, REG_UFS_SYS1CLK_1US);
+
+	core_clk_period_in_ns = NSEC_PER_SEC / core_clk_rate;
+	core_clk_period_in_ns <<= OFFSET_CLK_NS_REG;
+	core_clk_period_in_ns &= MASK_CLK_NS_REG;
+
+	switch (hs) {
+	case FASTAUTO_MODE:
+	case FAST_MODE:
+		if (rate == PA_HS_MODE_A) {
+			if (gear > ARRAY_SIZE(hs_fr_table_rA)) {
+				dev_err(hba->dev,
+					"%s: index %d exceeds table size %zu\n",
+					__func__, gear,
+					ARRAY_SIZE(hs_fr_table_rA));
+				goto out_error;
+			}
+			tx_clk_cycles_per_us = hs_fr_table_rA[gear-1][1];
+		} else if (rate == PA_HS_MODE_B) {
+			if (gear > ARRAY_SIZE(hs_fr_table_rB)) {
+				dev_err(hba->dev,
+					"%s: index %d exceeds table size %zu\n",
+					__func__, gear,
+					ARRAY_SIZE(hs_fr_table_rB));
+				goto out_error;
+			}
+			tx_clk_cycles_per_us = hs_fr_table_rB[gear-1][1];
+		} else {
+			dev_err(hba->dev, "%s: invalid rate = %d\n",
+				__func__, rate);
+			goto out_error;
+		}
+		break;
+	case SLOWAUTO_MODE:
+	case SLOW_MODE:
+		if (gear > ARRAY_SIZE(pwm_fr_table)) {
+			dev_err(hba->dev,
+					"%s: index %d exceeds table size %zu\n",
+					__func__, gear,
+					ARRAY_SIZE(pwm_fr_table));
+			goto out_error;
+		}
+		tx_clk_cycles_per_us = pwm_fr_table[gear-1][1];
+		break;
+	case UNCHANGED:
+	default:
+		dev_err(hba->dev, "%s: invalid mode = %d\n", __func__, hs);
+		goto out_error;
+	}
+
+	/* this register 2 fields shall be written at once */
+	ufshcd_writel(hba, core_clk_period_in_ns | tx_clk_cycles_per_us,
+						REG_UFS_TX_SYMBOL_CLK_NS_US);
+	goto out;
+
+out_error:
+	core_clk_rate = 0;
+out:
+	return core_clk_rate;
+}
+
+static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, bool status)
+{
+	unsigned long core_clk_rate = 0;
+	u32 core_clk_cycles_per_100ms;
+
+	switch (status) {
+	case PRE_CHANGE:
+		core_clk_rate = ufs_qcom_cfg_timers(hba, UFS_PWM_G1,
+						    SLOWAUTO_MODE, 0);
+		if (!core_clk_rate) {
+			dev_err(hba->dev, "%s: ufs_qcom_cfg_timers() failed\n",
+				__func__);
+			return -EINVAL;
+		}
+		core_clk_cycles_per_100ms =
+			(core_clk_rate / MSEC_PER_SEC) * 100;
+		ufshcd_writel(hba, core_clk_cycles_per_100ms,
+					REG_UFS_PA_LINK_STARTUP_TIMER);
+		break;
+	case POST_CHANGE:
+		ufs_qcom_link_startup_post_change(hba);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int ufs_qcom_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
+{
+	struct ufs_qcom_host *host = hba->priv;
+	struct phy *phy = host->generic_phy;
+	int ret = 0;
+
+	if (ufs_qcom_is_link_off(hba)) {
+		/*
+		 * Disable the tx/rx lane symbol clocks before PHY is
+		 * powered down as the PLL source should be disabled
+		 * after downstream clocks are disabled.
+		 */
+		ufs_qcom_disable_lane_clks(host);
+		phy_power_off(phy);
+
+		/* Assert PHY soft reset */
+		ufs_qcom_assert_reset(hba);
+		goto out;
+	}
+
+	/*
+	 * If UniPro link is not active, PHY ref_clk, main PHY analog power
+	 * rail and low noise analog power rail for PLL can be switched off.
+	 */
+	if (!ufs_qcom_is_link_active(hba))
+		phy_power_off(phy);
+
+out:
+	return ret;
+}
+
+static int ufs_qcom_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
+{
+	struct ufs_qcom_host *host = hba->priv;
+	struct phy *phy = host->generic_phy;
+	int err;
+
+	err = phy_power_on(phy);
+	if (err) {
+		dev_err(hba->dev, "%s: failed enabling regs, err = %d\n",
+			__func__, err);
+		goto out;
+	}
+
+	hba->is_sys_suspended = false;
+
+out:
+	return err;
+}
+
+struct ufs_qcom_dev_params {
+	u32 pwm_rx_gear;	/* pwm rx gear to work in */
+	u32 pwm_tx_gear;	/* pwm tx gear to work in */
+	u32 hs_rx_gear;		/* hs rx gear to work in */
+	u32 hs_tx_gear;		/* hs tx gear to work in */
+	u32 rx_lanes;		/* number of rx lanes */
+	u32 tx_lanes;		/* number of tx lanes */
+	u32 rx_pwr_pwm;		/* rx pwm working pwr */
+	u32 tx_pwr_pwm;		/* tx pwm working pwr */
+	u32 rx_pwr_hs;		/* rx hs working pwr */
+	u32 tx_pwr_hs;		/* tx hs working pwr */
+	u32 hs_rate;		/* rate A/B to work in HS */
+	u32 desired_working_mode;
+};
+
+static int ufs_qcom_get_pwr_dev_param(struct ufs_qcom_dev_params *qcom_param,
+				      struct ufs_pa_layer_attr *dev_max,
+				      struct ufs_pa_layer_attr *agreed_pwr)
+{
+	int min_qcom_gear;
+	int min_dev_gear;
+	bool is_dev_sup_hs = false;
+	bool is_qcom_max_hs = false;
+
+	if (dev_max->pwr_rx == FAST_MODE)
+		is_dev_sup_hs = true;
+
+	if (qcom_param->desired_working_mode == FAST) {
+		is_qcom_max_hs = true;
+		min_qcom_gear = min_t(u32, qcom_param->hs_rx_gear,
+				      qcom_param->hs_tx_gear);
+	} else {
+		min_qcom_gear = min_t(u32, qcom_param->pwm_rx_gear,
+				      qcom_param->pwm_tx_gear);
+	}
+
+	/*
+	 * device doesn't support HS but qcom_param->desired_working_mode is
+	 * HS, thus device and qcom_param don't agree
+	 */
+	if (!is_dev_sup_hs && is_qcom_max_hs) {
+		pr_err("%s: failed to agree on power mode (device doesn't support HS but requested power is HS)\n",
+			__func__);
+		return -ENOTSUPP;
+	} else if (is_dev_sup_hs && is_qcom_max_hs) {
+		/*
+		 * since device supports HS, it supports FAST_MODE.
+		 * since qcom_param->desired_working_mode is also HS
+		 * then final decision (FAST/FASTAUTO) is done according
+		 * to qcom_params as it is the restricting factor
+		 */
+		agreed_pwr->pwr_rx = agreed_pwr->pwr_tx =
+						qcom_param->rx_pwr_hs;
+	} else {
+		/*
+		 * here qcom_param->desired_working_mode is PWM.
+		 * it doesn't matter whether device supports HS or PWM,
+		 * in both cases qcom_param->desired_working_mode will
+		 * determine the mode
+		 */
+		 agreed_pwr->pwr_rx = agreed_pwr->pwr_tx =
+						qcom_param->rx_pwr_pwm;
+	}
+
+	/*
+	 * we would like tx to work in the minimum number of lanes
+	 * between device capability and vendor preferences.
+	 * the same decision will be made for rx
+	 */
+	agreed_pwr->lane_tx = min_t(u32, dev_max->lane_tx,
+						qcom_param->tx_lanes);
+	agreed_pwr->lane_rx = min_t(u32, dev_max->lane_rx,
+						qcom_param->rx_lanes);
+
+	/* device maximum gear is the minimum between device rx and tx gears */
+	min_dev_gear = min_t(u32, dev_max->gear_rx, dev_max->gear_tx);
+
+	/*
+	 * if both device capabilities and vendor pre-defined preferences are
+	 * both HS or both PWM then set the minimum gear to be the chosen
+	 * working gear.
+	 * if one is PWM and one is HS then the one that is PWM get to decide
+	 * what is the gear, as it is the one that also decided previously what
+	 * pwr the device will be configured to.
+	 */
+	if ((is_dev_sup_hs && is_qcom_max_hs) ||
+	    (!is_dev_sup_hs && !is_qcom_max_hs))
+		agreed_pwr->gear_rx = agreed_pwr->gear_tx =
+			min_t(u32, min_dev_gear, min_qcom_gear);
+	else if (!is_dev_sup_hs)
+		agreed_pwr->gear_rx = agreed_pwr->gear_tx = min_dev_gear;
+	else
+		agreed_pwr->gear_rx = agreed_pwr->gear_tx = min_qcom_gear;
+
+	agreed_pwr->hs_rate = qcom_param->hs_rate;
+	return 0;
+}
+
+static int ufs_qcom_update_bus_bw_vote(struct ufs_qcom_host *host)
+{
+	int vote;
+	int err = 0;
+	char mode[BUS_VECTOR_NAME_LEN];
+
+	ufs_qcom_get_speed_mode(&host->dev_req_params, mode);
+
+	vote = ufs_qcom_get_bus_vote(host, mode);
+	if (vote >= 0)
+		err = ufs_qcom_set_bus_vote(host, vote);
+	else
+		err = vote;
+
+	if (err)
+		dev_err(host->hba->dev, "%s: failed %d\n", __func__, err);
+	else
+		host->bus_vote.saved_vote = vote;
+	return err;
+}
+
+static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba,
+				bool status,
+				struct ufs_pa_layer_attr *dev_max_params,
+				struct ufs_pa_layer_attr *dev_req_params)
+{
+	u32 val;
+	struct ufs_qcom_host *host = hba->priv;
+	struct phy *phy = host->generic_phy;
+	struct ufs_qcom_dev_params ufs_qcom_cap;
+	int ret = 0;
+	int res = 0;
+
+	if (!dev_req_params) {
+		pr_err("%s: incoming dev_req_params is NULL\n", __func__);
+		ret = -EINVAL;
+		goto out;
+	}
+
+	switch (status) {
+	case PRE_CHANGE:
+		ufs_qcom_cap.tx_lanes = UFS_QCOM_LIMIT_NUM_LANES_TX;
+		ufs_qcom_cap.rx_lanes = UFS_QCOM_LIMIT_NUM_LANES_RX;
+		ufs_qcom_cap.hs_rx_gear = UFS_QCOM_LIMIT_HSGEAR_RX;
+		ufs_qcom_cap.hs_tx_gear = UFS_QCOM_LIMIT_HSGEAR_TX;
+		ufs_qcom_cap.pwm_rx_gear = UFS_QCOM_LIMIT_PWMGEAR_RX;
+		ufs_qcom_cap.pwm_tx_gear = UFS_QCOM_LIMIT_PWMGEAR_TX;
+		ufs_qcom_cap.rx_pwr_pwm = UFS_QCOM_LIMIT_RX_PWR_PWM;
+		ufs_qcom_cap.tx_pwr_pwm = UFS_QCOM_LIMIT_TX_PWR_PWM;
+		ufs_qcom_cap.rx_pwr_hs = UFS_QCOM_LIMIT_RX_PWR_HS;
+		ufs_qcom_cap.tx_pwr_hs = UFS_QCOM_LIMIT_TX_PWR_HS;
+		ufs_qcom_cap.hs_rate = UFS_QCOM_LIMIT_HS_RATE;
+		ufs_qcom_cap.desired_working_mode =
+					UFS_QCOM_LIMIT_DESIRED_MODE;
+
+		ret = ufs_qcom_get_pwr_dev_param(&ufs_qcom_cap,
+						 dev_max_params,
+						 dev_req_params);
+		if (ret) {
+			pr_err("%s: failed to determine capabilities\n",
+					__func__);
+			goto out;
+		}
+
+		break;
+	case POST_CHANGE:
+		if (!ufs_qcom_cfg_timers(hba, dev_req_params->gear_rx,
+					dev_req_params->pwr_rx,
+					dev_req_params->hs_rate)) {
+			dev_err(hba->dev, "%s: ufs_qcom_cfg_timers() failed\n",
+				__func__);
+			/*
+			 * we return error code at the end of the routine,
+			 * but continue to configure UFS_PHY_TX_LANE_ENABLE
+			 * and bus voting as usual
+			 */
+			ret = -EINVAL;
+		}
+
+		val = ~(MAX_U32 << dev_req_params->lane_tx);
+		res = ufs_qcom_phy_set_tx_lane_enable(phy, val);
+		if (res) {
+			dev_err(hba->dev, "%s: ufs_qcom_phy_set_tx_lane_enable() failed res = %d\n",
+				__func__, res);
+			ret = res;
+		}
+
+		/* cache the power mode parameters to use internally */
+		memcpy(&host->dev_req_params,
+				dev_req_params, sizeof(*dev_req_params));
+		ufs_qcom_update_bus_bw_vote(host);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+out:
+	return ret;
+}
+
+/**
+ * ufs_qcom_advertise_quirks - advertise the known QCOM UFS controller quirks
+ * @hba: host controller instance
+ *
+ * QCOM UFS host controller might have some non standard behaviours (quirks)
+ * than what is specified by UFSHCI specification. Advertise all such
+ * quirks to standard UFS host controller driver so standard takes them into
+ * account.
+ */
+static void ufs_qcom_advertise_quirks(struct ufs_hba *hba)
+{
+	u8 major;
+	u16 minor, step;
+
+	ufs_qcom_get_controller_revision(hba, &major, &minor, &step);
+
+	/*
+	 * TBD
+	 * here we should be advertising controller quirks according to
+	 * controller version.
+	 */
+}
+
+static int ufs_qcom_get_bus_vote(struct ufs_qcom_host *host,
+		const char *speed_mode)
+{
+	struct device *dev = host->hba->dev;
+	struct device_node *np = dev->of_node;
+	int err;
+	const char *key = "qcom,bus-vector-names";
+
+	if (!speed_mode) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	if (host->bus_vote.is_max_bw_needed && !!strcmp(speed_mode, "MIN"))
+		err = of_property_match_string(np, key, "MAX");
+	else
+		err = of_property_match_string(np, key, speed_mode);
+
+out:
+	if (err < 0)
+		dev_err(dev, "%s: Invalid %s mode %d\n",
+				__func__, speed_mode, err);
+	return err;
+}
+
+static int ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote)
+{
+	int err = 0;
+
+	if (vote != host->bus_vote.curr_vote)
+		host->bus_vote.curr_vote = vote;
+
+	return err;
+}
+
+static void ufs_qcom_get_speed_mode(struct ufs_pa_layer_attr *p, char *result)
+{
+	int gear = max_t(u32, p->gear_rx, p->gear_tx);
+	int lanes = max_t(u32, p->lane_rx, p->lane_tx);
+	int pwr;
+
+	/* default to PWM Gear 1, Lane 1 if power mode is not initialized */
+	if (!gear)
+		gear = 1;
+
+	if (!lanes)
+		lanes = 1;
+
+	if (!p->pwr_rx && !p->pwr_tx) {
+		pwr = SLOWAUTO_MODE;
+		snprintf(result, BUS_VECTOR_NAME_LEN, "MIN");
+	} else if (p->pwr_rx == FAST_MODE || p->pwr_rx == FASTAUTO_MODE ||
+		 p->pwr_tx == FAST_MODE || p->pwr_tx == FASTAUTO_MODE) {
+		pwr = FAST_MODE;
+		snprintf(result, BUS_VECTOR_NAME_LEN, "%s_R%s_G%d_L%d", "HS",
+			 p->hs_rate == PA_HS_MODE_B ? "B" : "A", gear, lanes);
+	} else {
+		pwr = SLOW_MODE;
+		snprintf(result, BUS_VECTOR_NAME_LEN, "%s_G%d_L%d",
+			 "PWM", gear, lanes);
+	}
+}
+
+static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on)
+{
+	struct ufs_qcom_host *host = hba->priv;
+	int err = 0;
+	int vote = 0;
+
+	/*
+	 * In case ufs_qcom_init() is not yet done, simply ignore.
+	 * This ufs_qcom_setup_clocks() shall be called from
+	 * ufs_qcom_init() after init is done.
+	 */
+	if (!host)
+		return 0;
+
+	if (on) {
+		err = ufs_qcom_phy_enable_iface_clk(host->generic_phy);
+		if (err)
+			goto out;
+
+		err = ufs_qcom_phy_enable_ref_clk(host->generic_phy);
+		if (err) {
+			dev_err(hba->dev, "%s enable phy ref clock failed, err=%d\n",
+				__func__, err);
+			ufs_qcom_phy_disable_iface_clk(host->generic_phy);
+			goto out;
+		}
+		/* enable the device ref clock */
+		ufs_qcom_phy_enable_dev_ref_clk(host->generic_phy);
+		vote = host->bus_vote.saved_vote;
+		if (vote == host->bus_vote.min_bw_vote)
+			ufs_qcom_update_bus_bw_vote(host);
+	} else {
+		/* M-PHY RMMI interface clocks can be turned off */
+		ufs_qcom_phy_disable_iface_clk(host->generic_phy);
+		if (!ufs_qcom_is_link_active(hba)) {
+			/* turn off UFS local PHY ref_clk */
+			ufs_qcom_phy_disable_ref_clk(host->generic_phy);
+			/* disable device ref_clk */
+			ufs_qcom_phy_disable_dev_ref_clk(host->generic_phy);
+		}
+		vote = host->bus_vote.min_bw_vote;
+	}
+
+	err = ufs_qcom_set_bus_vote(host, vote);
+	if (err)
+		dev_err(hba->dev, "%s: set bus vote failed %d\n",
+				__func__, err);
+
+out:
+	return err;
+}
+
+static ssize_t
+show_ufs_to_mem_max_bus_bw(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	struct ufs_hba *hba = dev_get_drvdata(dev);
+	struct ufs_qcom_host *host = hba->priv;
+
+	return snprintf(buf, PAGE_SIZE, "%u\n",
+			host->bus_vote.is_max_bw_needed);
+}
+
+static ssize_t
+store_ufs_to_mem_max_bus_bw(struct device *dev, struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	struct ufs_hba *hba = dev_get_drvdata(dev);
+	struct ufs_qcom_host *host = hba->priv;
+	uint32_t value;
+
+	if (!kstrtou32(buf, 0, &value)) {
+		host->bus_vote.is_max_bw_needed = !!value;
+		ufs_qcom_update_bus_bw_vote(host);
+	}
+
+	return count;
+}
+
+static int ufs_qcom_bus_register(struct ufs_qcom_host *host)
+{
+	int err;
+	struct device *dev = host->hba->dev;
+	struct device_node *np = dev->of_node;
+
+	err = of_property_count_strings(np, "qcom,bus-vector-names");
+	if (err < 0 ) {
+		dev_err(dev, "%s: qcom,bus-vector-names not specified correctly %d\n",
+				__func__, err);
+		goto out;
+	}
+
+	/* cache the vote index for minimum and maximum bandwidth */
+	host->bus_vote.min_bw_vote = ufs_qcom_get_bus_vote(host, "MIN");
+	host->bus_vote.max_bw_vote = ufs_qcom_get_bus_vote(host, "MAX");
+
+	host->bus_vote.max_bus_bw.show = show_ufs_to_mem_max_bus_bw;
+	host->bus_vote.max_bus_bw.store = store_ufs_to_mem_max_bus_bw;
+	sysfs_attr_init(&host->bus_vote.max_bus_bw.attr);
+	host->bus_vote.max_bus_bw.attr.name = "max_bus_bw";
+	host->bus_vote.max_bus_bw.attr.mode = S_IRUGO | S_IWUSR;
+	err = device_create_file(dev, &host->bus_vote.max_bus_bw);
+out:
+	return err;
+}
+
+#define	ANDROID_BOOT_DEV_MAX	30
+static char android_boot_dev[ANDROID_BOOT_DEV_MAX];
+static int get_android_boot_dev(char *str)
+{
+	strlcpy(android_boot_dev, str, ANDROID_BOOT_DEV_MAX);
+	return 1;
+}
+__setup("androidboot.bootdevice=", get_android_boot_dev);
+
+/**
+ * ufs_qcom_init - bind phy with controller
+ * @hba: host controller instance
+ *
+ * Binds PHY with controller and powers up PHY enabling clocks
+ * and regulators.
+ *
+ * Returns -EPROBE_DEFER if binding fails, returns negative error
+ * on phy power up failure and returns zero on success.
+ */
+static int ufs_qcom_init(struct ufs_hba *hba)
+{
+	int err;
+	struct device *dev = hba->dev;
+	struct ufs_qcom_host *host;
+
+	if (strlen(android_boot_dev) && strcmp(android_boot_dev, dev_name(dev)))
+		return -ENODEV;
+
+	host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+	if (!host) {
+		err = -ENOMEM;
+		dev_err(dev, "%s: no memory for qcom ufs host\n", __func__);
+		goto out;
+	}
+
+	host->hba = hba;
+	hba->priv = (void *)host;
+
+	host->generic_phy = devm_phy_get(dev, "ufsphy");
+
+	if (IS_ERR(host->generic_phy)) {
+		err = PTR_ERR(host->generic_phy);
+		dev_err(dev, "%s: PHY get failed %d\n", __func__, err);
+		goto out;
+	}
+
+	err = ufs_qcom_bus_register(host);
+	if (err)
+		goto out_host_free;
+
+	phy_init(host->generic_phy);
+	err = phy_power_on(host->generic_phy);
+	if (err)
+		goto out_unregister_bus;
+
+	err = ufs_qcom_init_lane_clks(host);
+	if (err)
+		goto out_disable_phy;
+
+	ufs_qcom_advertise_quirks(hba);
+
+	hba->caps |= UFSHCD_CAP_CLK_GATING | UFSHCD_CAP_CLK_SCALING;
+	hba->caps |= UFSHCD_CAP_AUTO_BKOPS_SUSPEND;
+
+	ufs_qcom_setup_clocks(hba, true);
+
+	if (hba->dev->id < MAX_UFS_QCOM_HOSTS)
+		ufs_qcom_hosts[hba->dev->id] = host;
+
+	goto out;
+
+out_disable_phy:
+	phy_power_off(host->generic_phy);
+out_unregister_bus:
+	phy_exit(host->generic_phy);
+out_host_free:
+	devm_kfree(dev, host);
+	hba->priv = NULL;
+out:
+	return err;
+}
+
+static void ufs_qcom_exit(struct ufs_hba *hba)
+{
+	struct ufs_qcom_host *host = hba->priv;
+
+	ufs_qcom_disable_lane_clks(host);
+	phy_power_off(host->generic_phy);
+}
+
+static
+void ufs_qcom_clk_scale_notify(struct ufs_hba *hba)
+{
+	struct ufs_qcom_host *host = hba->priv;
+	struct ufs_pa_layer_attr *dev_req_params = &host->dev_req_params;
+
+	if (!dev_req_params)
+		return;
+
+	ufs_qcom_cfg_timers(hba, dev_req_params->gear_rx,
+				dev_req_params->pwr_rx,
+				dev_req_params->hs_rate);
+}
+
+/**
+ * struct ufs_hba_qcom_vops - UFS QCOM specific variant operations
+ *
+ * The variant operations configure the necessary controller and PHY
+ * handshake during initialization.
+ */
+static const struct ufs_hba_variant_ops ufs_hba_qcom_vops = {
+	.name                   = "qcom",
+	.init                   = ufs_qcom_init,
+	.exit                   = ufs_qcom_exit,
+	.clk_scale_notify	= ufs_qcom_clk_scale_notify,
+	.setup_clocks           = ufs_qcom_setup_clocks,
+	.hce_enable_notify      = ufs_qcom_hce_enable_notify,
+	.link_startup_notify    = ufs_qcom_link_startup_notify,
+	.pwr_change_notify	= ufs_qcom_pwr_change_notify,
+	.suspend		= ufs_qcom_suspend,
+	.resume			= ufs_qcom_resume,
+};
+EXPORT_SYMBOL(ufs_hba_qcom_vops);
diff --git a/drivers/scsi/ufs/ufs-qcom.h b/drivers/scsi/ufs/ufs-qcom.h
new file mode 100644
index 0000000..9a6febd
--- /dev/null
+++ b/drivers/scsi/ufs/ufs-qcom.h
@@ -0,0 +1,170 @@
+/* Copyright (c) 2013-2015, 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.
+ *
+ */
+
+#ifndef UFS_QCOM_H_
+#define UFS_QCOM_H_
+
+#define MAX_UFS_QCOM_HOSTS	1
+#define MAX_U32                 (~(u32)0)
+#define MPHY_TX_FSM_STATE       0x41
+#define TX_FSM_HIBERN8          0x1
+#define HBRN8_POLL_TOUT_MS      100
+#define DEFAULT_CLK_RATE_HZ     1000000
+#define BUS_VECTOR_NAME_LEN     32
+
+#define UFS_HW_VER_MAJOR_SHFT	(28)
+#define UFS_HW_VER_MAJOR_MASK	(0x000F << UFS_HW_VER_MAJOR_SHFT)
+#define UFS_HW_VER_MINOR_SHFT	(16)
+#define UFS_HW_VER_MINOR_MASK	(0x0FFF << UFS_HW_VER_MINOR_SHFT)
+#define UFS_HW_VER_STEP_SHFT	(0)
+#define UFS_HW_VER_STEP_MASK	(0xFFFF << UFS_HW_VER_STEP_SHFT)
+
+/* vendor specific pre-defined parameters */
+#define SLOW 1
+#define FAST 2
+
+#define UFS_QCOM_LIMIT_NUM_LANES_RX	2
+#define UFS_QCOM_LIMIT_NUM_LANES_TX	2
+#define UFS_QCOM_LIMIT_HSGEAR_RX	UFS_HS_G2
+#define UFS_QCOM_LIMIT_HSGEAR_TX	UFS_HS_G2
+#define UFS_QCOM_LIMIT_PWMGEAR_RX	UFS_PWM_G4
+#define UFS_QCOM_LIMIT_PWMGEAR_TX	UFS_PWM_G4
+#define UFS_QCOM_LIMIT_RX_PWR_PWM	SLOW_MODE
+#define UFS_QCOM_LIMIT_TX_PWR_PWM	SLOW_MODE
+#define UFS_QCOM_LIMIT_RX_PWR_HS	FAST_MODE
+#define UFS_QCOM_LIMIT_TX_PWR_HS	FAST_MODE
+#define UFS_QCOM_LIMIT_HS_RATE		PA_HS_MODE_B
+#define UFS_QCOM_LIMIT_DESIRED_MODE	FAST
+
+/* QCOM UFS host controller vendor specific registers */
+enum {
+	REG_UFS_SYS1CLK_1US                 = 0xC0,
+	REG_UFS_TX_SYMBOL_CLK_NS_US         = 0xC4,
+	REG_UFS_LOCAL_PORT_ID_REG           = 0xC8,
+	REG_UFS_PA_ERR_CODE                 = 0xCC,
+	REG_UFS_RETRY_TIMER_REG             = 0xD0,
+	REG_UFS_PA_LINK_STARTUP_TIMER       = 0xD8,
+	REG_UFS_CFG1                        = 0xDC,
+	REG_UFS_CFG2                        = 0xE0,
+	REG_UFS_HW_VERSION                  = 0xE4,
+
+	UFS_DBG_RD_REG_UAWM			= 0x100,
+	UFS_DBG_RD_REG_UARM			= 0x200,
+	UFS_DBG_RD_REG_TXUC			= 0x300,
+	UFS_DBG_RD_REG_RXUC			= 0x400,
+	UFS_DBG_RD_REG_DFC			= 0x500,
+	UFS_DBG_RD_REG_TRLUT			= 0x600,
+	UFS_DBG_RD_REG_TMRLUT			= 0x700,
+	UFS_UFS_DBG_RD_REG_OCSC			= 0x800,
+
+	UFS_UFS_DBG_RD_DESC_RAM			= 0x1500,
+	UFS_UFS_DBG_RD_PRDT_RAM			= 0x1700,
+	UFS_UFS_DBG_RD_RESP_RAM			= 0x1800,
+	UFS_UFS_DBG_RD_EDTL_RAM			= 0x1900,
+};
+
+/* bit definitions for REG_UFS_CFG2 register */
+#define UAWM_HW_CGC_EN		(1 << 0)
+#define UARM_HW_CGC_EN		(1 << 1)
+#define TXUC_HW_CGC_EN		(1 << 2)
+#define RXUC_HW_CGC_EN		(1 << 3)
+#define DFC_HW_CGC_EN		(1 << 4)
+#define TRLUT_HW_CGC_EN		(1 << 5)
+#define TMRLUT_HW_CGC_EN	(1 << 6)
+#define OCSC_HW_CGC_EN		(1 << 7)
+
+#define REG_UFS_CFG2_CGC_EN_ALL (UAWM_HW_CGC_EN | UARM_HW_CGC_EN |\
+				 TXUC_HW_CGC_EN | RXUC_HW_CGC_EN |\
+				 DFC_HW_CGC_EN | TRLUT_HW_CGC_EN |\
+				 TMRLUT_HW_CGC_EN | OCSC_HW_CGC_EN)
+
+/* bit offset */
+enum {
+	OFFSET_UFS_PHY_SOFT_RESET           = 1,
+	OFFSET_CLK_NS_REG                   = 10,
+};
+
+/* bit masks */
+enum {
+	MASK_UFS_PHY_SOFT_RESET             = 0x2,
+	MASK_TX_SYMBOL_CLK_1US_REG          = 0x3FF,
+	MASK_CLK_NS_REG                     = 0xFFFC00,
+};
+
+enum ufs_qcom_phy_init_type {
+	UFS_PHY_INIT_FULL,
+	UFS_PHY_INIT_CFG_RESTORE,
+};
+
+static inline void
+ufs_qcom_get_controller_revision(struct ufs_hba *hba,
+				 u8 *major, u16 *minor, u16 *step)
+{
+	u32 ver = ufshcd_readl(hba, REG_UFS_HW_VERSION);
+
+	*major = (ver & UFS_HW_VER_MAJOR_MASK) >> UFS_HW_VER_MAJOR_SHFT;
+	*minor = (ver & UFS_HW_VER_MINOR_MASK) >> UFS_HW_VER_MINOR_SHFT;
+	*step = (ver & UFS_HW_VER_STEP_MASK) >> UFS_HW_VER_STEP_SHFT;
+};
+
+static inline void ufs_qcom_assert_reset(struct ufs_hba *hba)
+{
+	ufshcd_rmwl(hba, MASK_UFS_PHY_SOFT_RESET,
+			1 << OFFSET_UFS_PHY_SOFT_RESET, REG_UFS_CFG1);
+
+	/*
+	 * Make sure assertion of ufs phy reset is written to
+	 * register before returning
+	 */
+	mb();
+}
+
+static inline void ufs_qcom_deassert_reset(struct ufs_hba *hba)
+{
+	ufshcd_rmwl(hba, MASK_UFS_PHY_SOFT_RESET,
+			0 << OFFSET_UFS_PHY_SOFT_RESET, REG_UFS_CFG1);
+
+	/*
+	 * Make sure de-assertion of ufs phy reset is written to
+	 * register before returning
+	 */
+	mb();
+}
+
+struct ufs_qcom_bus_vote {
+	uint32_t client_handle;
+	uint32_t curr_vote;
+	int min_bw_vote;
+	int max_bw_vote;
+	int saved_vote;
+	bool is_max_bw_needed;
+	struct device_attribute max_bus_bw;
+};
+
+struct ufs_qcom_host {
+	struct phy *generic_phy;
+	struct ufs_hba *hba;
+	struct ufs_qcom_bus_vote bus_vote;
+	struct ufs_pa_layer_attr dev_req_params;
+	struct clk *rx_l0_sync_clk;
+	struct clk *tx_l0_sync_clk;
+	struct clk *rx_l1_sync_clk;
+	struct clk *tx_l1_sync_clk;
+	bool is_lane_clks_enabled;
+};
+
+#define ufs_qcom_is_link_off(hba) ufshcd_is_link_off(hba)
+#define ufs_qcom_is_link_active(hba) ufshcd_is_link_active(hba)
+#define ufs_qcom_is_link_hibern8(hba) ufshcd_is_link_hibern8(hba)
+
+#endif /* UFS_QCOM_H_ */
-- 
1.8.5.2

-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v7 4/5] phy: qcom-ufs: add support for 14nm phy
  2015-01-15 14:32 [PATCH v7 0/5] phy: qcom-ufs: add support for QUALCOMM Yaniv Gardi
@ 2015-01-15 14:32   ` Yaniv Gardi
  2015-01-15 14:32   ` Yaniv Gardi
                     ` (4 subsequent siblings)
  5 siblings, 0 replies; 27+ messages in thread
From: Yaniv Gardi @ 2015-01-15 14:32 UTC (permalink / raw)
  To: James.Bottomley, hch
  Cc: linux-kernel, linux-scsi, linux-arm-msm, santoshsy,
	linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Kishon Vijay Abraham I, Grant Likely, Rob Herring,
	open list:OPEN FIRMWARE AND...

This change adds a support for a 14nm qcom-ufs phy that is
required in platforms that use ufs-qcom controller.

Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>

---
 drivers/phy/Makefile                |   1 +
 drivers/phy/phy-qcom-ufs-qmp-14nm.c | 201 ++++++++++++++++++++++++++++++++++++
 drivers/phy/phy-qcom-ufs-qmp-14nm.h | 177 +++++++++++++++++++++++++++++++
 3 files changed, 379 insertions(+)
 create mode 100644 drivers/phy/phy-qcom-ufs-qmp-14nm.c
 create mode 100644 drivers/phy/phy-qcom-ufs-qmp-14nm.h

diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 781b2fa..cfbb720 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -36,3 +36,4 @@ obj-$(CONFIG_PHY_STIH407_USB)		+= phy-stih407-usb.o
 obj-$(CONFIG_PHY_STIH41X_USB)		+= phy-stih41x-usb.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-ufs-qmp-14nm.c b/drivers/phy/phy-qcom-ufs-qmp-14nm.c
new file mode 100644
index 0000000..f5fc50a
--- /dev/null
+++ b/drivers/phy/phy-qcom-ufs-qmp-14nm.c
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2013-2015, 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 "phy-qcom-ufs-qmp-14nm.h"
+
+#define UFS_PHY_NAME "ufs_phy_qmp_14nm"
+#define UFS_PHY_VDDA_PHY_UV	(925000)
+
+static
+int ufs_qcom_phy_qmp_14nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
+					bool is_rate_B)
+{
+	int tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A);
+	int tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
+	int err;
+
+	err = ufs_qcom_phy_calibrate(ufs_qcom_phy, phy_cal_table_rate_A,
+		tbl_size_A, phy_cal_table_rate_B, tbl_size_B, is_rate_B);
+
+	if (err)
+		dev_err(ufs_qcom_phy->dev,
+			"%s: ufs_qcom_phy_calibrate() failed %d\n",
+			__func__, err);
+	return err;
+}
+
+static
+void ufs_qcom_phy_qmp_14nm_advertise_quirks(struct ufs_qcom_phy *phy_common)
+{
+	phy_common->quirks =
+		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
+}
+
+static int ufs_qcom_phy_qmp_14nm_init(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy_qmp_14nm *phy = phy_get_drvdata(generic_phy);
+	struct ufs_qcom_phy *phy_common = &phy->common_cfg;
+	int err;
+
+	err = ufs_qcom_phy_init_clks(generic_phy, phy_common);
+	if (err) {
+		dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_clks() failed %d\n",
+			__func__, err);
+		goto out;
+	}
+
+	err = ufs_qcom_phy_init_vregulators(generic_phy, phy_common);
+	if (err) {
+		dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_vregulators() failed %d\n",
+			__func__, err);
+		goto out;
+	}
+	phy_common->vdda_phy.max_uV = UFS_PHY_VDDA_PHY_UV;
+	phy_common->vdda_phy.min_uV = UFS_PHY_VDDA_PHY_UV;
+
+	ufs_qcom_phy_qmp_14nm_advertise_quirks(phy_common);
+
+out:
+	return err;
+}
+
+static
+void ufs_qcom_phy_qmp_14nm_power_control(struct ufs_qcom_phy *phy, bool val)
+{
+	writel_relaxed(val ? 0x1 : 0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
+	/*
+	 * Before any transactions involving PHY, ensure PHY knows
+	 * that it's analog rail is powered ON (or OFF).
+	 */
+	mb();
+}
+
+static inline
+void ufs_qcom_phy_qmp_14nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val)
+{
+	/*
+	 * 14nm PHY does not have TX_LANE_ENABLE register.
+	 * Implement this function so as not to propagate error to caller.
+	 */
+}
+
+static inline void ufs_qcom_phy_qmp_14nm_start_serdes(struct ufs_qcom_phy *phy)
+{
+	u32 tmp;
+
+	tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
+	tmp &= ~MASK_SERDES_START;
+	tmp |= (1 << OFFSET_SERDES_START);
+	writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
+	/* Ensure register value is committed */
+	mb();
+}
+
+static int ufs_qcom_phy_qmp_14nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
+{
+	int err = 0;
+	u32 val;
+
+	err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS,
+		val, (val & MASK_PCS_READY), 10, 1000000);
+	if (err)
+		dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n",
+			__func__, err);
+	return err;
+}
+
+static struct phy_ops ufs_qcom_phy_qmp_14nm_phy_ops = {
+	.init		= ufs_qcom_phy_qmp_14nm_init,
+	.exit		= ufs_qcom_phy_exit,
+	.power_on	= ufs_qcom_phy_power_on,
+	.power_off	= ufs_qcom_phy_power_off,
+	.owner		= THIS_MODULE,
+};
+
+static struct ufs_qcom_phy_specific_ops phy_14nm_ops = {
+	.calibrate_phy		= ufs_qcom_phy_qmp_14nm_phy_calibrate,
+	.start_serdes		= ufs_qcom_phy_qmp_14nm_start_serdes,
+	.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_14nm_is_pcs_ready,
+	.set_tx_lane_enable	= ufs_qcom_phy_qmp_14nm_set_tx_lane_enable,
+	.power_control		= ufs_qcom_phy_qmp_14nm_power_control,
+};
+
+static int ufs_qcom_phy_qmp_14nm_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct phy *generic_phy;
+	struct ufs_qcom_phy_qmp_14nm *phy;
+	int err = 0;
+
+	phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+	if (!phy) {
+		dev_err(dev, "%s: failed to allocate phy\n", __func__);
+		err = -ENOMEM;
+		goto out;
+	}
+
+	generic_phy = ufs_qcom_phy_generic_probe(pdev, &phy->common_cfg,
+				&ufs_qcom_phy_qmp_14nm_phy_ops, &phy_14nm_ops);
+
+	if (!generic_phy) {
+		dev_err(dev, "%s: ufs_qcom_phy_generic_probe() failed\n",
+			__func__);
+		err = -EIO;
+		goto out;
+	}
+
+	phy_set_drvdata(generic_phy, phy);
+
+	strlcpy(phy->common_cfg.name, UFS_PHY_NAME,
+		sizeof(phy->common_cfg.name));
+
+out:
+	return err;
+}
+
+static int ufs_qcom_phy_qmp_14nm_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct phy *generic_phy = to_phy(dev);
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+	int err = 0;
+
+	err = ufs_qcom_phy_remove(generic_phy, ufs_qcom_phy);
+	if (err)
+		dev_err(dev, "%s: ufs_qcom_phy_remove failed = %d\n",
+			__func__, err);
+
+	return err;
+}
+
+static const struct of_device_id ufs_qcom_phy_qmp_14nm_of_match[] = {
+	{.compatible = "qcom,ufs-phy-qmp-14nm"},
+	{},
+};
+MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_14nm_of_match);
+
+static struct platform_driver ufs_qcom_phy_qmp_14nm_driver = {
+	.probe = ufs_qcom_phy_qmp_14nm_probe,
+	.remove = ufs_qcom_phy_qmp_14nm_remove,
+	.driver = {
+		.of_match_table = ufs_qcom_phy_qmp_14nm_of_match,
+		.name = "ufs_qcom_phy_qmp_14nm",
+		.owner = THIS_MODULE,
+	},
+};
+
+module_platform_driver(ufs_qcom_phy_qmp_14nm_driver);
+
+MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 14nm");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/phy-qcom-ufs-qmp-14nm.h b/drivers/phy/phy-qcom-ufs-qmp-14nm.h
new file mode 100644
index 0000000..3aefdba
--- /dev/null
+++ b/drivers/phy/phy-qcom-ufs-qmp-14nm.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2013-2015, 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.
+ *
+ */
+
+#ifndef UFS_QCOM_PHY_QMP_14NM_H_
+#define UFS_QCOM_PHY_QMP_14NM_H_
+
+#include "phy-qcom-ufs-i.h"
+
+/* QCOM UFS PHY control registers */
+#define COM_OFF(x)	(0x000 + x)
+#define PHY_OFF(x)	(0xC00 + x)
+#define TX_OFF(n, x)	(0x400 + (0x400 * n) + x)
+#define RX_OFF(n, x)	(0x600 + (0x400 * n) + x)
+
+/* UFS PHY QSERDES COM registers */
+#define QSERDES_COM_BG_TIMER			COM_OFF(0x0C)
+#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN		COM_OFF(0x34)
+#define QSERDES_COM_SYS_CLK_CTRL		COM_OFF(0x3C)
+#define QSERDES_COM_LOCK_CMP1_MODE0		COM_OFF(0x4C)
+#define QSERDES_COM_LOCK_CMP2_MODE0		COM_OFF(0x50)
+#define QSERDES_COM_LOCK_CMP3_MODE0		COM_OFF(0x54)
+#define QSERDES_COM_LOCK_CMP1_MODE1		COM_OFF(0x58)
+#define QSERDES_COM_LOCK_CMP2_MODE1		COM_OFF(0x5C)
+#define QSERDES_COM_LOCK_CMP3_MODE1		COM_OFF(0x60)
+#define QSERDES_COM_CP_CTRL_MODE0		COM_OFF(0x78)
+#define QSERDES_COM_CP_CTRL_MODE1		COM_OFF(0x7C)
+#define QSERDES_COM_PLL_RCTRL_MODE0		COM_OFF(0x84)
+#define QSERDES_COM_PLL_RCTRL_MODE1		COM_OFF(0x88)
+#define QSERDES_COM_PLL_CCTRL_MODE0		COM_OFF(0x90)
+#define QSERDES_COM_PLL_CCTRL_MODE1		COM_OFF(0x94)
+#define QSERDES_COM_SYSCLK_EN_SEL		COM_OFF(0xAC)
+#define QSERDES_COM_RESETSM_CNTRL		COM_OFF(0xB4)
+#define QSERDES_COM_LOCK_CMP_EN			COM_OFF(0xC8)
+#define QSERDES_COM_LOCK_CMP_CFG		COM_OFF(0xCC)
+#define QSERDES_COM_DEC_START_MODE0		COM_OFF(0xD0)
+#define QSERDES_COM_DEC_START_MODE1		COM_OFF(0xD4)
+#define QSERDES_COM_DIV_FRAC_START1_MODE0	COM_OFF(0xDC)
+#define QSERDES_COM_DIV_FRAC_START2_MODE0	COM_OFF(0xE0)
+#define QSERDES_COM_DIV_FRAC_START3_MODE0	COM_OFF(0xE4)
+#define QSERDES_COM_DIV_FRAC_START1_MODE1	COM_OFF(0xE8)
+#define QSERDES_COM_DIV_FRAC_START2_MODE1	COM_OFF(0xEC)
+#define QSERDES_COM_DIV_FRAC_START3_MODE1	COM_OFF(0xF0)
+#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0	COM_OFF(0x108)
+#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0	COM_OFF(0x10C)
+#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1	COM_OFF(0x110)
+#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1	COM_OFF(0x114)
+#define QSERDES_COM_VCO_TUNE_CTRL		COM_OFF(0x124)
+#define QSERDES_COM_VCO_TUNE_MAP		COM_OFF(0x128)
+#define QSERDES_COM_VCO_TUNE1_MODE0		COM_OFF(0x12C)
+#define QSERDES_COM_VCO_TUNE2_MODE0		COM_OFF(0x130)
+#define QSERDES_COM_VCO_TUNE1_MODE1		COM_OFF(0x134)
+#define QSERDES_COM_VCO_TUNE2_MODE1		COM_OFF(0x138)
+#define QSERDES_COM_VCO_TUNE_TIMER1		COM_OFF(0x144)
+#define QSERDES_COM_VCO_TUNE_TIMER2		COM_OFF(0x148)
+#define QSERDES_COM_CLK_SELECT			COM_OFF(0x174)
+#define QSERDES_COM_HSCLK_SEL			COM_OFF(0x178)
+#define QSERDES_COM_CORECLK_DIV			COM_OFF(0x184)
+#define QSERDES_COM_CORE_CLK_EN			COM_OFF(0x18C)
+#define QSERDES_COM_CMN_CONFIG			COM_OFF(0x194)
+#define QSERDES_COM_SVS_MODE_CLK_SEL		COM_OFF(0x19C)
+#define QSERDES_COM_CORECLK_DIV_MODE1		COM_OFF(0x1BC)
+
+/* UFS PHY registers */
+#define UFS_PHY_PHY_START			PHY_OFF(0x00)
+#define UFS_PHY_POWER_DOWN_CONTROL		PHY_OFF(0x04)
+#define UFS_PHY_PCS_READY_STATUS		PHY_OFF(0x168)
+
+/* UFS PHY TX registers */
+#define QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN	TX_OFF(0, 0x68)
+#define QSERDES_TX_LANE_MODE				TX_OFF(0, 0x94)
+
+/* UFS PHY RX registers */
+#define QSERDES_RX_UCDR_FASTLOCK_FO_GAIN	RX_OFF(0, 0x40)
+#define QSERDES_RX_RX_TERM_BW			RX_OFF(0, 0x90)
+#define QSERDES_RX_RX_EQ_GAIN1_LSB		RX_OFF(0, 0xC4)
+#define QSERDES_RX_RX_EQ_GAIN1_MSB		RX_OFF(0, 0xC8)
+#define QSERDES_RX_RX_EQ_GAIN2_LSB		RX_OFF(0, 0xCC)
+#define QSERDES_RX_RX_EQ_GAIN2_MSB		RX_OFF(0, 0xD0)
+#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2	RX_OFF(0, 0xD8)
+#define QSERDES_RX_SIGDET_CNTRL			RX_OFF(0, 0x114)
+#define QSERDES_RX_SIGDET_LVL			RX_OFF(0, 0x118)
+#define QSERDES_RX_SIGDET_DEGLITCH_CNTRL	RX_OFF(0, 0x11C)
+#define QSERDES_RX_RX_INTERFACE_MODE		RX_OFF(0, 0x12C)
+
+/*
+ * This structure represents the 14nm specific phy.
+ * common_cfg MUST remain the first field in this structure
+ * in case extra fields are added. This way, when calling
+ * get_ufs_qcom_phy() of generic phy, we can extract the
+ * common phy structure (struct ufs_qcom_phy) out of it
+ * regardless of the relevant specific phy.
+ */
+struct ufs_qcom_phy_qmp_14nm {
+	struct ufs_qcom_phy common_cfg;
+};
+
+static struct ufs_qcom_phy_calibration phy_cal_table_rate_A[] = {
+	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CMN_CONFIG, 0x0e),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL, 0xd7),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CLK_SELECT, 0x30),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYS_CLK_CTRL, 0x06),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BG_TIMER, 0x0a),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_HSCLK_SEL, 0x05),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV, 0x0a),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV_MODE1, 0x0a),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_EN, 0x01),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_CTRL, 0x10),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x20),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORE_CLK_EN, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_CFG, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_TIMER1, 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_TIMER2, 0x3f),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x14),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SVS_MODE_CLK_SEL, 0x05),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE0, 0x82),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE0, 0x0b),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE0, 0x28),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE0, 0x28),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE0, 0x02),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE0, 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE0, 0x0c),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP3_MODE0, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE1, 0x98),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1_MODE1, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2_MODE1, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3_MODE1, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE1, 0x0b),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE1, 0x16),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE1, 0x28),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE1, 0x80),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE1, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE1, 0xd6),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE1, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE1, 0x32),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE1, 0x0f),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP3_MODE1, 0x00),
+
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN, 0x45),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE, 0x02),
+
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_LVL, 0x24),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_CNTRL, 0x02),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_INTERFACE_MODE, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x18),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_UCDR_FASTLOCK_FO_GAIN, 0x0B),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_TERM_BW, 0x5B),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB, 0xFF),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB, 0x3F),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB, 0xFF),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB, 0x0F),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0E),
+};
+
+static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x54),
+};
+
+#endif
-- 
1.8.5.2

-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v7 4/5] phy: qcom-ufs: add support for 14nm phy
@ 2015-01-15 14:32   ` Yaniv Gardi
  0 siblings, 0 replies; 27+ messages in thread
From: Yaniv Gardi @ 2015-01-15 14:32 UTC (permalink / raw)
  To: James.Bottomley, hch
  Cc: linux-kernel, linux-scsi, linux-arm-msm, santoshsy,
	linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Kishon Vijay Abraham I, Grant Likely, Rob Herring,
	open list:OPEN FIRMWARE AND...

This change adds a support for a 14nm qcom-ufs phy that is
required in platforms that use ufs-qcom controller.

Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>

---
 drivers/phy/Makefile                |   1 +
 drivers/phy/phy-qcom-ufs-qmp-14nm.c | 201 ++++++++++++++++++++++++++++++++++++
 drivers/phy/phy-qcom-ufs-qmp-14nm.h | 177 +++++++++++++++++++++++++++++++
 3 files changed, 379 insertions(+)
 create mode 100644 drivers/phy/phy-qcom-ufs-qmp-14nm.c
 create mode 100644 drivers/phy/phy-qcom-ufs-qmp-14nm.h

diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 781b2fa..cfbb720 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -36,3 +36,4 @@ obj-$(CONFIG_PHY_STIH407_USB)		+= phy-stih407-usb.o
 obj-$(CONFIG_PHY_STIH41X_USB)		+= phy-stih41x-usb.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-ufs-qmp-14nm.c b/drivers/phy/phy-qcom-ufs-qmp-14nm.c
new file mode 100644
index 0000000..f5fc50a
--- /dev/null
+++ b/drivers/phy/phy-qcom-ufs-qmp-14nm.c
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2013-2015, 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 "phy-qcom-ufs-qmp-14nm.h"
+
+#define UFS_PHY_NAME "ufs_phy_qmp_14nm"
+#define UFS_PHY_VDDA_PHY_UV	(925000)
+
+static
+int ufs_qcom_phy_qmp_14nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
+					bool is_rate_B)
+{
+	int tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A);
+	int tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
+	int err;
+
+	err = ufs_qcom_phy_calibrate(ufs_qcom_phy, phy_cal_table_rate_A,
+		tbl_size_A, phy_cal_table_rate_B, tbl_size_B, is_rate_B);
+
+	if (err)
+		dev_err(ufs_qcom_phy->dev,
+			"%s: ufs_qcom_phy_calibrate() failed %d\n",
+			__func__, err);
+	return err;
+}
+
+static
+void ufs_qcom_phy_qmp_14nm_advertise_quirks(struct ufs_qcom_phy *phy_common)
+{
+	phy_common->quirks =
+		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
+}
+
+static int ufs_qcom_phy_qmp_14nm_init(struct phy *generic_phy)
+{
+	struct ufs_qcom_phy_qmp_14nm *phy = phy_get_drvdata(generic_phy);
+	struct ufs_qcom_phy *phy_common = &phy->common_cfg;
+	int err;
+
+	err = ufs_qcom_phy_init_clks(generic_phy, phy_common);
+	if (err) {
+		dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_clks() failed %d\n",
+			__func__, err);
+		goto out;
+	}
+
+	err = ufs_qcom_phy_init_vregulators(generic_phy, phy_common);
+	if (err) {
+		dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_vregulators() failed %d\n",
+			__func__, err);
+		goto out;
+	}
+	phy_common->vdda_phy.max_uV = UFS_PHY_VDDA_PHY_UV;
+	phy_common->vdda_phy.min_uV = UFS_PHY_VDDA_PHY_UV;
+
+	ufs_qcom_phy_qmp_14nm_advertise_quirks(phy_common);
+
+out:
+	return err;
+}
+
+static
+void ufs_qcom_phy_qmp_14nm_power_control(struct ufs_qcom_phy *phy, bool val)
+{
+	writel_relaxed(val ? 0x1 : 0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
+	/*
+	 * Before any transactions involving PHY, ensure PHY knows
+	 * that it's analog rail is powered ON (or OFF).
+	 */
+	mb();
+}
+
+static inline
+void ufs_qcom_phy_qmp_14nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val)
+{
+	/*
+	 * 14nm PHY does not have TX_LANE_ENABLE register.
+	 * Implement this function so as not to propagate error to caller.
+	 */
+}
+
+static inline void ufs_qcom_phy_qmp_14nm_start_serdes(struct ufs_qcom_phy *phy)
+{
+	u32 tmp;
+
+	tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
+	tmp &= ~MASK_SERDES_START;
+	tmp |= (1 << OFFSET_SERDES_START);
+	writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
+	/* Ensure register value is committed */
+	mb();
+}
+
+static int ufs_qcom_phy_qmp_14nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
+{
+	int err = 0;
+	u32 val;
+
+	err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS,
+		val, (val & MASK_PCS_READY), 10, 1000000);
+	if (err)
+		dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n",
+			__func__, err);
+	return err;
+}
+
+static struct phy_ops ufs_qcom_phy_qmp_14nm_phy_ops = {
+	.init		= ufs_qcom_phy_qmp_14nm_init,
+	.exit		= ufs_qcom_phy_exit,
+	.power_on	= ufs_qcom_phy_power_on,
+	.power_off	= ufs_qcom_phy_power_off,
+	.owner		= THIS_MODULE,
+};
+
+static struct ufs_qcom_phy_specific_ops phy_14nm_ops = {
+	.calibrate_phy		= ufs_qcom_phy_qmp_14nm_phy_calibrate,
+	.start_serdes		= ufs_qcom_phy_qmp_14nm_start_serdes,
+	.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_14nm_is_pcs_ready,
+	.set_tx_lane_enable	= ufs_qcom_phy_qmp_14nm_set_tx_lane_enable,
+	.power_control		= ufs_qcom_phy_qmp_14nm_power_control,
+};
+
+static int ufs_qcom_phy_qmp_14nm_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct phy *generic_phy;
+	struct ufs_qcom_phy_qmp_14nm *phy;
+	int err = 0;
+
+	phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+	if (!phy) {
+		dev_err(dev, "%s: failed to allocate phy\n", __func__);
+		err = -ENOMEM;
+		goto out;
+	}
+
+	generic_phy = ufs_qcom_phy_generic_probe(pdev, &phy->common_cfg,
+				&ufs_qcom_phy_qmp_14nm_phy_ops, &phy_14nm_ops);
+
+	if (!generic_phy) {
+		dev_err(dev, "%s: ufs_qcom_phy_generic_probe() failed\n",
+			__func__);
+		err = -EIO;
+		goto out;
+	}
+
+	phy_set_drvdata(generic_phy, phy);
+
+	strlcpy(phy->common_cfg.name, UFS_PHY_NAME,
+		sizeof(phy->common_cfg.name));
+
+out:
+	return err;
+}
+
+static int ufs_qcom_phy_qmp_14nm_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct phy *generic_phy = to_phy(dev);
+	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+	int err = 0;
+
+	err = ufs_qcom_phy_remove(generic_phy, ufs_qcom_phy);
+	if (err)
+		dev_err(dev, "%s: ufs_qcom_phy_remove failed = %d\n",
+			__func__, err);
+
+	return err;
+}
+
+static const struct of_device_id ufs_qcom_phy_qmp_14nm_of_match[] = {
+	{.compatible = "qcom,ufs-phy-qmp-14nm"},
+	{},
+};
+MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_14nm_of_match);
+
+static struct platform_driver ufs_qcom_phy_qmp_14nm_driver = {
+	.probe = ufs_qcom_phy_qmp_14nm_probe,
+	.remove = ufs_qcom_phy_qmp_14nm_remove,
+	.driver = {
+		.of_match_table = ufs_qcom_phy_qmp_14nm_of_match,
+		.name = "ufs_qcom_phy_qmp_14nm",
+		.owner = THIS_MODULE,
+	},
+};
+
+module_platform_driver(ufs_qcom_phy_qmp_14nm_driver);
+
+MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 14nm");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/phy-qcom-ufs-qmp-14nm.h b/drivers/phy/phy-qcom-ufs-qmp-14nm.h
new file mode 100644
index 0000000..3aefdba
--- /dev/null
+++ b/drivers/phy/phy-qcom-ufs-qmp-14nm.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2013-2015, 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.
+ *
+ */
+
+#ifndef UFS_QCOM_PHY_QMP_14NM_H_
+#define UFS_QCOM_PHY_QMP_14NM_H_
+
+#include "phy-qcom-ufs-i.h"
+
+/* QCOM UFS PHY control registers */
+#define COM_OFF(x)	(0x000 + x)
+#define PHY_OFF(x)	(0xC00 + x)
+#define TX_OFF(n, x)	(0x400 + (0x400 * n) + x)
+#define RX_OFF(n, x)	(0x600 + (0x400 * n) + x)
+
+/* UFS PHY QSERDES COM registers */
+#define QSERDES_COM_BG_TIMER			COM_OFF(0x0C)
+#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN		COM_OFF(0x34)
+#define QSERDES_COM_SYS_CLK_CTRL		COM_OFF(0x3C)
+#define QSERDES_COM_LOCK_CMP1_MODE0		COM_OFF(0x4C)
+#define QSERDES_COM_LOCK_CMP2_MODE0		COM_OFF(0x50)
+#define QSERDES_COM_LOCK_CMP3_MODE0		COM_OFF(0x54)
+#define QSERDES_COM_LOCK_CMP1_MODE1		COM_OFF(0x58)
+#define QSERDES_COM_LOCK_CMP2_MODE1		COM_OFF(0x5C)
+#define QSERDES_COM_LOCK_CMP3_MODE1		COM_OFF(0x60)
+#define QSERDES_COM_CP_CTRL_MODE0		COM_OFF(0x78)
+#define QSERDES_COM_CP_CTRL_MODE1		COM_OFF(0x7C)
+#define QSERDES_COM_PLL_RCTRL_MODE0		COM_OFF(0x84)
+#define QSERDES_COM_PLL_RCTRL_MODE1		COM_OFF(0x88)
+#define QSERDES_COM_PLL_CCTRL_MODE0		COM_OFF(0x90)
+#define QSERDES_COM_PLL_CCTRL_MODE1		COM_OFF(0x94)
+#define QSERDES_COM_SYSCLK_EN_SEL		COM_OFF(0xAC)
+#define QSERDES_COM_RESETSM_CNTRL		COM_OFF(0xB4)
+#define QSERDES_COM_LOCK_CMP_EN			COM_OFF(0xC8)
+#define QSERDES_COM_LOCK_CMP_CFG		COM_OFF(0xCC)
+#define QSERDES_COM_DEC_START_MODE0		COM_OFF(0xD0)
+#define QSERDES_COM_DEC_START_MODE1		COM_OFF(0xD4)
+#define QSERDES_COM_DIV_FRAC_START1_MODE0	COM_OFF(0xDC)
+#define QSERDES_COM_DIV_FRAC_START2_MODE0	COM_OFF(0xE0)
+#define QSERDES_COM_DIV_FRAC_START3_MODE0	COM_OFF(0xE4)
+#define QSERDES_COM_DIV_FRAC_START1_MODE1	COM_OFF(0xE8)
+#define QSERDES_COM_DIV_FRAC_START2_MODE1	COM_OFF(0xEC)
+#define QSERDES_COM_DIV_FRAC_START3_MODE1	COM_OFF(0xF0)
+#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0	COM_OFF(0x108)
+#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0	COM_OFF(0x10C)
+#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1	COM_OFF(0x110)
+#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1	COM_OFF(0x114)
+#define QSERDES_COM_VCO_TUNE_CTRL		COM_OFF(0x124)
+#define QSERDES_COM_VCO_TUNE_MAP		COM_OFF(0x128)
+#define QSERDES_COM_VCO_TUNE1_MODE0		COM_OFF(0x12C)
+#define QSERDES_COM_VCO_TUNE2_MODE0		COM_OFF(0x130)
+#define QSERDES_COM_VCO_TUNE1_MODE1		COM_OFF(0x134)
+#define QSERDES_COM_VCO_TUNE2_MODE1		COM_OFF(0x138)
+#define QSERDES_COM_VCO_TUNE_TIMER1		COM_OFF(0x144)
+#define QSERDES_COM_VCO_TUNE_TIMER2		COM_OFF(0x148)
+#define QSERDES_COM_CLK_SELECT			COM_OFF(0x174)
+#define QSERDES_COM_HSCLK_SEL			COM_OFF(0x178)
+#define QSERDES_COM_CORECLK_DIV			COM_OFF(0x184)
+#define QSERDES_COM_CORE_CLK_EN			COM_OFF(0x18C)
+#define QSERDES_COM_CMN_CONFIG			COM_OFF(0x194)
+#define QSERDES_COM_SVS_MODE_CLK_SEL		COM_OFF(0x19C)
+#define QSERDES_COM_CORECLK_DIV_MODE1		COM_OFF(0x1BC)
+
+/* UFS PHY registers */
+#define UFS_PHY_PHY_START			PHY_OFF(0x00)
+#define UFS_PHY_POWER_DOWN_CONTROL		PHY_OFF(0x04)
+#define UFS_PHY_PCS_READY_STATUS		PHY_OFF(0x168)
+
+/* UFS PHY TX registers */
+#define QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN	TX_OFF(0, 0x68)
+#define QSERDES_TX_LANE_MODE				TX_OFF(0, 0x94)
+
+/* UFS PHY RX registers */
+#define QSERDES_RX_UCDR_FASTLOCK_FO_GAIN	RX_OFF(0, 0x40)
+#define QSERDES_RX_RX_TERM_BW			RX_OFF(0, 0x90)
+#define QSERDES_RX_RX_EQ_GAIN1_LSB		RX_OFF(0, 0xC4)
+#define QSERDES_RX_RX_EQ_GAIN1_MSB		RX_OFF(0, 0xC8)
+#define QSERDES_RX_RX_EQ_GAIN2_LSB		RX_OFF(0, 0xCC)
+#define QSERDES_RX_RX_EQ_GAIN2_MSB		RX_OFF(0, 0xD0)
+#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2	RX_OFF(0, 0xD8)
+#define QSERDES_RX_SIGDET_CNTRL			RX_OFF(0, 0x114)
+#define QSERDES_RX_SIGDET_LVL			RX_OFF(0, 0x118)
+#define QSERDES_RX_SIGDET_DEGLITCH_CNTRL	RX_OFF(0, 0x11C)
+#define QSERDES_RX_RX_INTERFACE_MODE		RX_OFF(0, 0x12C)
+
+/*
+ * This structure represents the 14nm specific phy.
+ * common_cfg MUST remain the first field in this structure
+ * in case extra fields are added. This way, when calling
+ * get_ufs_qcom_phy() of generic phy, we can extract the
+ * common phy structure (struct ufs_qcom_phy) out of it
+ * regardless of the relevant specific phy.
+ */
+struct ufs_qcom_phy_qmp_14nm {
+	struct ufs_qcom_phy common_cfg;
+};
+
+static struct ufs_qcom_phy_calibration phy_cal_table_rate_A[] = {
+	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CMN_CONFIG, 0x0e),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL, 0xd7),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CLK_SELECT, 0x30),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYS_CLK_CTRL, 0x06),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BG_TIMER, 0x0a),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_HSCLK_SEL, 0x05),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV, 0x0a),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV_MODE1, 0x0a),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_EN, 0x01),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_CTRL, 0x10),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x20),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORE_CLK_EN, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_CFG, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_TIMER1, 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_TIMER2, 0x3f),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x14),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SVS_MODE_CLK_SEL, 0x05),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE0, 0x82),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE0, 0x0b),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE0, 0x28),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE0, 0x28),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE0, 0x02),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE0, 0xff),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE0, 0x0c),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP3_MODE0, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE1, 0x98),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1_MODE1, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2_MODE1, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3_MODE1, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE1, 0x0b),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE1, 0x16),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE1, 0x28),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE1, 0x80),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE1, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE1, 0xd6),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE1, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE1, 0x32),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE1, 0x0f),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP3_MODE1, 0x00),
+
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN, 0x45),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE, 0x02),
+
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_LVL, 0x24),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_CNTRL, 0x02),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_INTERFACE_MODE, 0x00),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x18),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_UCDR_FASTLOCK_FO_GAIN, 0x0B),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_TERM_BW, 0x5B),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB, 0xFF),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB, 0x3F),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB, 0xFF),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB, 0x0F),
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0E),
+};
+
+static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
+	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x54),
+};
+
+#endif
-- 
1.8.5.2

-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v7 5/5] scsi: ufs-qcom-ice: add Inline Crypto Engine (ICE) support for UFS
  2015-01-15 14:32 [PATCH v7 0/5] phy: qcom-ufs: add support for QUALCOMM Yaniv Gardi
                   ` (3 preceding siblings ...)
  2015-01-15 14:32   ` Yaniv Gardi
@ 2015-01-15 14:32 ` Yaniv Gardi
  2015-01-15 15:21     ` Dov Levenglick
  2015-01-23  8:48   ` Paul Bolle
  2015-01-15 15:03   ` Dov Levenglick
  5 siblings, 2 replies; 27+ messages in thread
From: Yaniv Gardi @ 2015-01-15 14:32 UTC (permalink / raw)
  To: James.Bottomley, hch
  Cc: linux-kernel, linux-scsi, linux-arm-msm, santoshsy,
	linux-scsi-owner, subhashj, ygardi, noag, draviv, Yaniv Gardi,
	Vinayak Holikatti, James E.J. Bottomley

From: Yaniv Gardi <ygardi@qti.qualcomm.com>

In-order to enhance storage encryption performance,
an Inline Cryptographic Engine is introduced to UFS.
This patch adds in-line encryption capabilities to the UFS
driver.

Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>

---
 drivers/scsi/ufs/Kconfig        |  12 +
 drivers/scsi/ufs/Makefile       |   1 +
 drivers/scsi/ufs/ufs-qcom-ice.c | 520 ++++++++++++++++++++++++++++++++++++++++
 drivers/scsi/ufs/ufs-qcom-ice.h | 113 +++++++++
 drivers/scsi/ufs/ufs-qcom.c     |  56 ++++-
 drivers/scsi/ufs/ufs-qcom.h     |  25 ++
 6 files changed, 726 insertions(+), 1 deletion(-)
 create mode 100644 drivers/scsi/ufs/ufs-qcom-ice.c
 create mode 100644 drivers/scsi/ufs/ufs-qcom-ice.h

diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig
index 8a1f4b3..ecf34ed 100644
--- a/drivers/scsi/ufs/Kconfig
+++ b/drivers/scsi/ufs/Kconfig
@@ -83,3 +83,15 @@ config SCSI_UFS_QCOM
 
 	  Select this if you have UFS controller on QCOM chipset.
 	  If unsure, say N.
+
+config SCSI_UFS_QCOM_ICE
+	bool "QCOM specific hooks to Inline Crypto Engine for UFS driver"
+	depends on SCSI_UFS_QCOM && CRYPTO_DEV_QCOM_ICE
+	help
+	  This selects the QCOM specific additions to support Inline Crypto
+	  Engine (ICE).
+	  ICE accelerates the crypto operations and maintains the high UFS
+	  performance.
+
+	  Select this if you have ICE supported for UFS on QCOM chipset.
+	  If unsure, say N.
diff --git a/drivers/scsi/ufs/Makefile b/drivers/scsi/ufs/Makefile
index 8303bcc..31adca5 100644
--- a/drivers/scsi/ufs/Makefile
+++ b/drivers/scsi/ufs/Makefile
@@ -1,5 +1,6 @@
 # UFSHCD makefile
 obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-qcom.o
+obj-$(CONFIG_SCSI_UFS_QCOM_ICE) += ufs-qcom-ice.o
 obj-$(CONFIG_SCSI_UFSHCD) += ufshcd.o
 obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o
 obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o
diff --git a/drivers/scsi/ufs/ufs-qcom-ice.c b/drivers/scsi/ufs/ufs-qcom-ice.c
new file mode 100644
index 0000000..9202b73
--- /dev/null
+++ b/drivers/scsi/ufs/ufs-qcom-ice.c
@@ -0,0 +1,520 @@
+/* Copyright (c) 2014-2015, 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/of.h>
+#include <linux/async.h>
+#include <linux/blkdev.h>
+#include <crypto/ice.h>
+
+#include "ufs-qcom-ice.h"
+#include "ufshcd.h"
+
+#define UFS_QCOM_CRYPTO_LABEL "ufs-qcom-crypto"
+/* Timeout waiting for ICE initialization, that requires TZ access */
+#define UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS 500
+
+static void ufs_qcom_ice_success_cb(void *host_ctrl,
+				enum ice_event_completion evt)
+{
+	struct ufs_qcom_host *qcom_host = (struct ufs_qcom_host *)host_ctrl;
+
+	if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_DISABLED &&
+	    evt == ICE_INIT_COMPLETION)
+		qcom_host->ice.state = UFS_QCOM_ICE_STATE_ACTIVE;
+	 else if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_SUSPENDED &&
+		   evt == ICE_RESUME_COMPLETION)
+		qcom_host->ice.state = UFS_QCOM_ICE_STATE_ACTIVE;
+
+	complete(&qcom_host->ice.async_done);
+}
+
+static void ufs_qcom_ice_error_cb(void *host_ctrl, enum ice_error_code evt)
+{
+	struct ufs_qcom_host *qcom_host = (struct ufs_qcom_host *)host_ctrl;
+
+	dev_err(qcom_host->hba->dev, "%s: Error in ice operation %d",
+		__func__, evt);
+
+	if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_ACTIVE)
+		qcom_host->ice.state = UFS_QCOM_ICE_STATE_DISABLED;
+
+	complete(&qcom_host->ice.async_done);
+}
+
+static struct platform_device *ufs_qcom_ice_get_pdevice(struct device *ufs_dev)
+{
+	struct device_node *node;
+	struct platform_device *ice_pdev = NULL;
+
+	node = of_parse_phandle(ufs_dev->of_node, UFS_QCOM_CRYPTO_LABEL, 0);
+
+	if (!node) {
+		dev_err(ufs_dev, "%s: ufs-qcom-crypto property not specified\n",
+			__func__);
+		goto out;
+	}
+
+	ice_pdev = qcom_ice_get_pdevice(node);
+out:
+	return ice_pdev;
+}
+
+static
+struct qcom_ice_variant_ops *ufs_qcom_ice_get_vops(struct device *ufs_dev)
+{
+	struct qcom_ice_variant_ops *ice_vops = NULL;
+	struct device_node *node;
+
+	node = of_parse_phandle(ufs_dev->of_node, UFS_QCOM_CRYPTO_LABEL, 0);
+
+	if (!node) {
+		dev_err(ufs_dev, "%s: ufs-qcom-crypto property not specified\n",
+			__func__);
+		goto out;
+	}
+
+	ice_vops = qcom_ice_get_variant_ops(node);
+
+	if (!ice_vops)
+		dev_err(ufs_dev, "%s: invalid ice_vops\n", __func__);
+
+	of_node_put(node);
+out:
+	return ice_vops;
+}
+
+/**
+ * ufs_qcom_ice_get_dev() - sets pointers to ICE data structs in UFS QCom host
+ * @qcom_host:	Pointer to a UFS QCom internal host structure.
+ *
+ * Sets ICE platform device pointer and ICE vops structure
+ * corresponding to the current UFS device.
+ *
+ * Return: -EINVAL in-case of invalid input parameters:
+ *  qcom_host, qcom_host->hba or qcom_host->hba->dev
+ *         -ENODEV in-case ICE device is not required
+ *         -EPROBE_DEFER in-case ICE is required and hasn't been probed yet
+ *         0 otherwise
+ */
+int ufs_qcom_ice_get_dev(struct ufs_qcom_host *qcom_host)
+{
+	struct device *ufs_dev;
+	int err = 0;
+
+	if (!qcom_host || !qcom_host->hba || !qcom_host->hba->dev) {
+		pr_err("%s: invalid qcom_host %p or qcom_host->hba or qcom_host->hba->dev\n",
+			__func__, qcom_host);
+		err = -EINVAL;
+		goto out;
+	}
+
+	ufs_dev = qcom_host->hba->dev;
+
+	qcom_host->ice.vops  = ufs_qcom_ice_get_vops(ufs_dev);
+	qcom_host->ice.pdev = ufs_qcom_ice_get_pdevice(ufs_dev);
+
+	if (qcom_host->ice.pdev == ERR_PTR(-EPROBE_DEFER)) {
+		dev_err(ufs_dev, "%s: ICE device not probed yet\n",
+			__func__);
+		qcom_host->ice.pdev = NULL;
+		qcom_host->ice.vops = NULL;
+		err = -EPROBE_DEFER;
+		goto out;
+	}
+
+	if (!qcom_host->ice.pdev || !qcom_host->ice.vops) {
+		dev_err(ufs_dev, "%s: invalid platform device %p or vops %p\n",
+			__func__, qcom_host->ice.pdev, qcom_host->ice.vops);
+		qcom_host->ice.pdev = NULL;
+		qcom_host->ice.vops = NULL;
+		err = -ENODEV;
+		goto out;
+	}
+
+	qcom_host->ice.state = UFS_QCOM_ICE_STATE_DISABLED;
+
+out:
+	return err;
+
+}
+
+/**
+ * ufs_qcom_ice_init() - initializes the ICE-UFS interface and ICE device
+ * @qcom_host:	Pointer to a UFS QCom internal host structure.
+ *		qcom_host, qcom_host->hba and qcom_host->hba->dev should all
+ *		be valid pointers.
+ *
+ * Return: -EINVAL in-case of an error
+ *         0 otherwise
+ */
+int ufs_qcom_ice_init(struct ufs_qcom_host *qcom_host)
+{
+	struct device *ufs_dev = qcom_host->hba->dev;
+	int err = -EINVAL;
+
+	init_completion(&qcom_host->ice.async_done);
+	err = qcom_host->ice.vops->init(qcom_host->ice.pdev,
+				qcom_host,
+				ufs_qcom_ice_success_cb,
+				ufs_qcom_ice_error_cb);
+	if (err) {
+		dev_err(ufs_dev, "%s: ice init failed. err = %d\n",
+			__func__, err);
+		goto out;
+	}
+
+	if (!wait_for_completion_timeout(&qcom_host->ice.async_done,
+			msecs_to_jiffies(UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS))) {
+		dev_err(qcom_host->hba->dev,
+			"%s: error. got timeout after %d ms\n",
+			__func__, UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS);
+		err = -ETIMEDOUT;
+		goto out;
+	}
+
+	if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) {
+		dev_err(qcom_host->hba->dev,
+			"%s: error. ice.state (%d) is not in active state\n",
+			__func__, qcom_host->ice.state);
+		err = -EINVAL;
+	}
+
+out:
+	return err;
+}
+
+static inline bool ufs_qcom_is_data_cmd(char cmd_op, bool is_write)
+{
+	if (is_write) {
+		if (cmd_op == WRITE_6 || cmd_op == WRITE_10 ||
+		    cmd_op == WRITE_16)
+			return true;
+	} else {
+		if (cmd_op == READ_6 || cmd_op == READ_10 ||
+		    cmd_op == READ_16)
+			return true;
+	}
+
+	return false;
+}
+
+/**
+ * ufs_qcom_ice_cfg() - configures UFS's ICE registers for an ICE transaction
+ * @qcom_host:	Pointer to a UFS QCom internal host structure.
+ *		qcom_host, qcom_host->hba and qcom_host->hba->dev should all
+ *		be valid pointers.
+ * @cmd:	Pointer to a valid scsi command. cmd->request should also be
+ *              a valid pointer.
+ *
+ * Return: -EINVAL in-case of an error
+ *         0 otherwise
+ */
+int ufs_qcom_ice_cfg(struct ufs_qcom_host *qcom_host, struct scsi_cmnd *cmd)
+{
+	struct device *dev = qcom_host->hba->dev;
+	int err = 0;
+	struct ice_data_setting ice_set;
+	unsigned int slot = 0;
+	sector_t lba = 0;
+	unsigned int ctrl_info_2_val = 0;
+	unsigned int bypass = 0;
+	struct request *req;
+	char cmd_op;
+
+	if (!qcom_host->ice.pdev || !qcom_host->ice.vops) {
+		dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
+		goto out;
+	}
+
+	if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) {
+		dev_err(dev, "%s: ice state (%d) is not active\n",
+			__func__, qcom_host->ice.state);
+		return -EINVAL;
+	}
+
+	req = cmd->request;
+	if (req->bio)
+		lba = req->bio->bi_sector;
+
+	slot = req->tag;
+	if (slot < 0 || slot > qcom_host->hba->nutrs) {
+		dev_err(dev, "%s: slot (%d) is out of boundaries (0...%d)\n",
+			__func__, slot, qcom_host->hba->nutrs);
+		return -EINVAL;
+	}
+
+	memset(&ice_set, sizeof(ice_set), 0);
+	if (qcom_host->ice.vops->config) {
+		err = qcom_host->ice.vops->config(qcom_host->ice.pdev,
+							req, &ice_set);
+
+		if (err) {
+			dev_err(dev, "%s: error in ice_vops->config %d\n",
+				__func__, err);
+			goto out;
+		}
+	}
+
+	cmd_op = cmd->cmnd[0];
+
+#define UFS_QCOM_DIR_WRITE	true
+#define UFS_QCOM_DIR_READ	false
+	/* if non data command, bypass shall be enabled */
+	if (!ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_WRITE) &&
+	    !ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_READ))
+		bypass = UFS_QCOM_ICE_ENABLE_BYPASS;
+	/* if writing data command */
+	else if (ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_WRITE))
+		bypass = ice_set.encr_bypass ? UFS_QCOM_ICE_ENABLE_BYPASS :
+						UFS_QCOM_ICE_DISABLE_BYPASS;
+	/* if reading data command */
+	else if (ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_READ))
+		bypass = ice_set.decr_bypass ? UFS_QCOM_ICE_ENABLE_BYPASS :
+						UFS_QCOM_ICE_DISABLE_BYPASS;
+
+	/* Configure ICE index */
+	ctrl_info_2_val =
+		(ice_set.crypto_data.key_index &
+		 MASK_UFS_QCOM_ICE_CTRL_INFO_2_KEY_INDEX)
+		 << OFFSET_UFS_QCOM_ICE_CTRL_INFO_2_KEY_INDEX;
+
+	/* Configure data unit size of transfer request */
+	ctrl_info_2_val |=
+		(UFS_QCOM_ICE_TR_DATA_UNIT_4_KB &
+		 MASK_UFS_QCOM_ICE_CTRL_INFO_2_CDU)
+		 << OFFSET_UFS_QCOM_ICE_CTRL_INFO_2_CDU;
+
+	/* Configure ICE bypass mode */
+	ctrl_info_2_val |=
+		(bypass & MASK_UFS_QCOM_ICE_CTRL_INFO_2_BYPASS)
+		 << OFFSET_UFS_QCOM_ICE_CTRL_INFO_2_BYPASS;
+
+	ufshcd_writel(qcom_host->hba, lba,
+		     (REG_UFS_QCOM_ICE_CTRL_INFO_1_n + 8 * slot));
+
+	ufshcd_writel(qcom_host->hba, ctrl_info_2_val,
+		     (REG_UFS_QCOM_ICE_CTRL_INFO_2_n + 8 * slot));
+
+	/*
+	 * Ensure UFS-ICE registers are being configured
+	 * before next operation, otherwise UFS Host Controller might
+	 * set get errors
+	 */
+	mb();
+out:
+	return err;
+}
+
+/**
+ * ufs_qcom_ice_reset() - resets UFS-ICE interface and ICE device
+ * @qcom_host:	Pointer to a UFS QCom internal host structure.
+ *		qcom_host, qcom_host->hba and qcom_host->hba->dev should all
+ *		be valid pointers.
+ *
+ * Return: -EINVAL in-case of an error
+ *         0 otherwise
+ */
+int ufs_qcom_ice_reset(struct ufs_qcom_host *qcom_host)
+{
+	struct device *dev = qcom_host->hba->dev;
+	int err = 0;
+
+	if (!qcom_host->ice.pdev) {
+		dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
+		goto out;
+	}
+
+	if (!qcom_host->ice.vops) {
+		dev_err(dev, "%s: invalid ice_vops\n", __func__);
+		return -EINVAL;
+	}
+
+	if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE)
+		goto out;
+
+	init_completion(&qcom_host->ice.async_done);
+
+	if (qcom_host->ice.vops->reset) {
+		err = qcom_host->ice.vops->reset(qcom_host->ice.pdev);
+		if (err) {
+			dev_err(dev, "%s: ice_vops->reset failed. err %d\n",
+				__func__, err);
+			goto out;
+		}
+	}
+
+	if (!wait_for_completion_timeout(&qcom_host->ice.async_done,
+	     msecs_to_jiffies(UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS))) {
+		dev_err(dev,
+			"%s: error. got timeout after %d ms\n",
+			__func__, UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS);
+		err = -ETIMEDOUT;
+	}
+
+out:
+	return err;
+}
+
+/**
+ * ufs_qcom_ice_resume() - resumes UFS-ICE interface and ICE device from power
+ * collapse
+ * @qcom_host:	Pointer to a UFS QCom internal host structure.
+ *		qcom_host, qcom_host->hba and qcom_host->hba->dev should all
+ *		be valid pointers.
+ *
+ * Return: -EINVAL in-case of an error
+ *         0 otherwise
+ */
+int ufs_qcom_ice_resume(struct ufs_qcom_host *qcom_host)
+{
+	struct device *dev = qcom_host->hba->dev;
+	int err = 0;
+
+	if (!qcom_host->ice.pdev) {
+		dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
+		goto out;
+	}
+
+	if (qcom_host->ice.state !=
+			UFS_QCOM_ICE_STATE_SUSPENDED) {
+		goto out;
+	}
+
+	if (!qcom_host->ice.vops) {
+		dev_err(dev, "%s: invalid ice_vops\n", __func__);
+		return -EINVAL;
+	}
+
+	init_completion(&qcom_host->ice.async_done);
+
+	if (qcom_host->ice.vops->resume) {
+		err = qcom_host->ice.vops->resume(qcom_host->ice.pdev);
+		if (err) {
+			dev_err(dev, "%s: ice_vops->resume failed. err %d\n",
+				__func__, err);
+			return -EINVAL;
+		}
+	}
+
+	if (!wait_for_completion_timeout(&qcom_host->ice.async_done,
+			msecs_to_jiffies(UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS))) {
+		dev_err(dev,
+			"%s: error. got timeout after %d ms\n",
+			__func__, UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS);
+		err = -ETIMEDOUT;
+		goto out;
+	}
+
+	if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE)
+		err = -EINVAL;
+out:
+	return err;
+}
+
+/**
+ * ufs_qcom_ice_suspend() - suspends UFS-ICE interface and ICE device
+ * @qcom_host:	Pointer to a UFS QCom internal host structure.
+ *		qcom_host, qcom_host->hba and qcom_host->hba->dev should all
+ *		be valid pointers.
+ *
+ * Return: -EINVAL in-case of an error
+ *         0 otherwise
+ */
+int ufs_qcom_ice_suspend(struct ufs_qcom_host *qcom_host)
+{
+	struct device *dev = qcom_host->hba->dev;
+	int err = 0;
+
+	if (!qcom_host->ice.pdev) {
+		dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
+		goto out;
+	}
+
+	if (qcom_host->ice.vops->suspend) {
+		err = qcom_host->ice.vops->suspend(qcom_host->ice.pdev);
+		if (err) {
+			dev_err(qcom_host->hba->dev,
+				"%s: ice_vops->suspend failed. err %d\n",
+				__func__, err);
+			return -EINVAL;
+		}
+	}
+
+	if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_ACTIVE) {
+		qcom_host->ice.state = UFS_QCOM_ICE_STATE_SUSPENDED;
+	} else if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_DISABLED) {
+		dev_err(qcom_host->hba->dev,
+				"%s: ice state is invalid: disabled\n",
+				__func__);
+		err = -EINVAL;
+	}
+
+out:
+	return err;
+}
+
+/**
+ * ufs_qcom_ice_get_status() - returns the status of an ICE transaction
+ * @qcom_host:	Pointer to a UFS QCom internal host structure.
+ *		qcom_host, qcom_host->hba and qcom_host->hba->dev should all
+ *		be valid pointers.
+ * @ice_status:	Pointer to a valid output parameter.
+ *		< 0 in case of ICE transaction failure.
+ *		0 otherwise.
+ *
+ * Return: -EINVAL in-case of an error
+ *         0 otherwise
+ */
+int ufs_qcom_ice_get_status(struct ufs_qcom_host *qcom_host, int *ice_status)
+{
+	struct device *dev = NULL;
+	int err = 0;
+	int stat = -EINVAL;
+
+	ice_status = 0;
+
+	dev = qcom_host->hba->dev;
+	if (!dev) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	if (!qcom_host->ice.pdev) {
+		dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
+		goto out;
+	}
+
+	if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	if (!qcom_host->ice.vops) {
+		dev_err(dev, "%s: invalid ice_vops\n", __func__);
+		return -EINVAL;
+	}
+
+	if (qcom_host->ice.vops->status) {
+		stat = qcom_host->ice.vops->status(qcom_host->ice.pdev);
+		if (stat < 0) {
+			dev_err(dev, "%s: ice_vops->status failed. stat %d\n",
+				__func__, stat);
+			err = -EINVAL;
+			goto out;
+		}
+
+		*ice_status = stat;
+	}
+
+out:
+	return err;
+}
diff --git a/drivers/scsi/ufs/ufs-qcom-ice.h b/drivers/scsi/ufs/ufs-qcom-ice.h
new file mode 100644
index 0000000..5ccbf5f
--- /dev/null
+++ b/drivers/scsi/ufs/ufs-qcom-ice.h
@@ -0,0 +1,113 @@
+/* Copyright (c) 2014-2015, 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.
+ *
+ */
+
+#ifndef _UFS_QCOM_ICE_H_
+#define _UFS_QCOM_ICE_H_
+
+#include <scsi/scsi_cmnd.h>
+
+#include "ufs-qcom.h"
+
+/*
+ * UFS host controller ICE registers. There are n [0..31]
+ * of each of these registers
+ */
+enum {
+	REG_UFS_QCOM_ICE_CTRL_INFO_1_n           = 0x2204,
+	REG_UFS_QCOM_ICE_CTRL_INFO_2_n           = 0x2208,
+};
+
+/* UFS QCOM ICE CTRL Info 2 register offset */
+enum {
+	OFFSET_UFS_QCOM_ICE_CTRL_INFO_2_BYPASS     = 0,
+	OFFSET_UFS_QCOM_ICE_CTRL_INFO_2_KEY_INDEX  = 0x1,
+	OFFSET_UFS_QCOM_ICE_CTRL_INFO_2_CDU        = 0x6,
+};
+
+/* UFS QCOM ICE CTRL Info 2 register masks */
+enum {
+	MASK_UFS_QCOM_ICE_CTRL_INFO_2_BYPASS     = 0x1,
+	MASK_UFS_QCOM_ICE_CTRL_INFO_2_KEY_INDEX  = 0x1F,
+	MASK_UFS_QCOM_ICE_CTRL_INFO_2_CDU        = 0x8,
+};
+
+/* UFS QCOM ICE encryption/decryption bypass state */
+enum {
+	UFS_QCOM_ICE_DISABLE_BYPASS  = 0,
+	UFS_QCOM_ICE_ENABLE_BYPASS = 1,
+};
+
+/* UFS QCOM ICE Crypto Data Unit of target DUN of Transfer Request */
+enum {
+	UFS_QCOM_ICE_TR_DATA_UNIT_512_B          = 0,
+	UFS_QCOM_ICE_TR_DATA_UNIT_1_KB           = 1,
+	UFS_QCOM_ICE_TR_DATA_UNIT_2_KB           = 2,
+	UFS_QCOM_ICE_TR_DATA_UNIT_4_KB           = 3,
+	UFS_QCOM_ICE_TR_DATA_UNIT_8_KB           = 4,
+	UFS_QCOM_ICE_TR_DATA_UNIT_16_KB          = 5,
+	UFS_QCOM_ICE_TR_DATA_UNIT_32_KB          = 6,
+};
+
+/* UFS QCOM ICE internal state */
+enum {
+	UFS_QCOM_ICE_STATE_DISABLED   = 0,
+	UFS_QCOM_ICE_STATE_ACTIVE     = 1,
+	UFS_QCOM_ICE_STATE_SUSPENDED  = 2,
+};
+
+#ifdef CONFIG_SCSI_UFS_QCOM_ICE
+int ufs_qcom_ice_get_dev(struct ufs_qcom_host *qcom_host);
+int ufs_qcom_ice_init(struct ufs_qcom_host *qcom_host);
+int ufs_qcom_ice_cfg(struct ufs_qcom_host *qcom_host, struct scsi_cmnd *cmd);
+int ufs_qcom_ice_reset(struct ufs_qcom_host *qcom_host);
+int ufs_qcom_ice_resume(struct ufs_qcom_host *qcom_host);
+int ufs_qcom_ice_suspend(struct ufs_qcom_host *qcom_host);
+int ufs_qcom_ice_get_status(struct ufs_qcom_host *qcom_host, int *ice_status);
+#else
+inline int ufs_qcom_ice_get_dev(struct ufs_qcom_host *qcom_host)
+{
+	if (qcom_host) {
+		qcom_host->ice.pdev = NULL;
+		qcom_host->ice.vops = NULL;
+	}
+	return -ENODEV;
+}
+inline int ufs_qcom_ice_init(struct ufs_qcom_host *qcom_host)
+{
+	return 0;
+}
+inline int ufs_qcom_ice_cfg(struct ufs_qcom_host *qcom_host,
+			    struct scsi_cmnd *cmd)
+{
+	return 0;
+}
+inline int ufs_qcom_ice_reset(struct ufs_qcom_host *qcom_host)
+{
+	return 0;
+}
+inline int ufs_qcom_ice_resume(struct ufs_qcom_host *qcom_host)
+{
+	return 0;
+}
+inline int ufs_qcom_ice_suspend(struct ufs_qcom_host *qcom_host)
+{
+	return 0;
+}
+inline int ufs_qcom_ice_get_status(struct ufs_qcom_host *qcom_host,
+				   int *ice_status)
+{
+	return 0;
+}
+#endif /* CONFIG_SCSI_UFS_QCOM_ICE */
+
+#endif /* UFS_QCOM_ICE_H_ */
diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c
index 9217af9..fd01255 100644
--- a/drivers/scsi/ufs/ufs-qcom.c
+++ b/drivers/scsi/ufs/ufs-qcom.c
@@ -22,6 +22,7 @@
 #include "unipro.h"
 #include "ufs-qcom.h"
 #include "ufshci.h"
+#include "ufs-qcom-ice.h"
 
 static struct ufs_qcom_host *ufs_qcom_hosts[MAX_UFS_QCOM_HOSTS];
 
@@ -294,6 +295,13 @@ static int ufs_qcom_hce_enable_notify(struct ufs_hba *hba, bool status)
 		/* check if UFS PHY moved from DISABLED to HIBERN8 */
 		err = ufs_qcom_check_hibern8(hba);
 		ufs_qcom_enable_hw_clk_gating(hba);
+		if (!err) {
+			err = ufs_qcom_ice_reset(host);
+			if (err)
+				dev_err(hba->dev,
+					"%s: ufs_qcom_ice_reset() failed %d\n",
+					__func__, err);
+		}
 
 		break;
 	default:
@@ -453,6 +461,10 @@ static int ufs_qcom_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
 		 */
 		ufs_qcom_disable_lane_clks(host);
 		phy_power_off(phy);
+		ret = ufs_qcom_ice_suspend(host);
+		if (ret)
+			dev_err(hba->dev, "%s: failed ufs_qcom_ice_suspend %d\n",
+					__func__, ret);
 
 		/* Assert PHY soft reset */
 		ufs_qcom_assert_reset(hba);
@@ -463,8 +475,10 @@ static int ufs_qcom_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
 	 * If UniPro link is not active, PHY ref_clk, main PHY analog power
 	 * rail and low noise analog power rail for PLL can be switched off.
 	 */
-	if (!ufs_qcom_is_link_active(hba))
+	if (!ufs_qcom_is_link_active(hba)) {
 		phy_power_off(phy);
+		ufs_qcom_ice_suspend(host);
+	}
 
 out:
 	return ret;
@@ -483,6 +497,13 @@ static int ufs_qcom_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
 		goto out;
 	}
 
+	err = ufs_qcom_ice_resume(host);
+	if (err) {
+		dev_err(hba->dev, "%s: ufs_qcom_ice_resume failed, err = %d\n",
+			__func__, err);
+		goto out;
+	}
+
 	hba->is_sys_suspended = false;
 
 out:
@@ -917,6 +938,30 @@ static int ufs_qcom_init(struct ufs_hba *hba)
 	host->hba = hba;
 	hba->priv = (void *)host;
 
+	err = ufs_qcom_ice_get_dev(host);
+	if (err == -EPROBE_DEFER) {
+		/*
+		 * UFS driver might be probed before ICE driver does.
+		 * In that case we would like to return EPROBE_DEFER code
+		 * in order to delay its probing.
+		 */
+		dev_err(dev, "%s: required ICE device not probed yet err = %d\n",
+			__func__, err);
+		goto out_host_free;
+
+	} else if (err == -ENODEV) {
+		/*
+		 * ICE device is not enabled in DTS file. No need for further
+		 * initialization of ICE driver.
+		 */
+		dev_warn(dev, "%s: ICE device is not enabled",
+			__func__);
+	} else if (err) {
+		dev_err(dev, "%s: ufs_qcom_ice_get_dev failed %d\n",
+			__func__, err);
+		goto out_host_free;
+	}
+
 	host->generic_phy = devm_phy_get(dev, "ufsphy");
 
 	if (IS_ERR(host->generic_phy)) {
@@ -944,6 +989,15 @@ static int ufs_qcom_init(struct ufs_hba *hba)
 	hba->caps |= UFSHCD_CAP_AUTO_BKOPS_SUSPEND;
 
 	ufs_qcom_setup_clocks(hba, true);
+	if (host->ice.pdev) {
+		err = ufs_qcom_ice_init(host);
+		if (err) {
+			dev_err(dev, "%s: ICE driver initialization failed (%d)\n",
+				__func__, err);
+			device_remove_file(dev, &host->bus_vote.max_bus_bw);
+			goto out_disable_phy;
+		}
+	}
 
 	if (hba->dev->id < MAX_UFS_QCOM_HOSTS)
 		ufs_qcom_hosts[hba->dev->id] = host;
diff --git a/drivers/scsi/ufs/ufs-qcom.h b/drivers/scsi/ufs/ufs-qcom.h
index 9a6febd..8340f2c 100644
--- a/drivers/scsi/ufs/ufs-qcom.h
+++ b/drivers/scsi/ufs/ufs-qcom.h
@@ -151,6 +151,30 @@ struct ufs_qcom_bus_vote {
 	struct device_attribute max_bus_bw;
 };
 
+/**
+ * struct ufs_qcom_ice_data - ICE related information
+ * @vops:	pointer to variant operations of ICE
+ * @async_done:	completion for supporting ICE's driver asynchronous nature
+ * @pdev:	pointer to the proper ICE platform device
+ * @state:      UFS-ICE interface's internal state (see
+ *       ufs-qcom-ice.h for possible internal states)
+ * @quirks:     UFS-ICE interface related quirks
+ */
+struct ufs_qcom_ice_data {
+	struct qcom_ice_variant_ops *vops;
+	struct completion async_done;
+	struct platform_device *pdev;
+	int state;
+
+	/*
+	 * If UFS host controller should handle cryptographic engine's
+	 * errors, enables this quirk.
+	 */
+	#define UFS_QCOM_ICE_QUIRK_HANDLE_CRYPTO_ENGINE_ERRORS	UFS_BIT(0)
+
+	u16 quirks;
+};
+
 struct ufs_qcom_host {
 	struct phy *generic_phy;
 	struct ufs_hba *hba;
@@ -161,6 +185,7 @@ struct ufs_qcom_host {
 	struct clk *rx_l1_sync_clk;
 	struct clk *tx_l1_sync_clk;
 	bool is_lane_clks_enabled;
+	struct ufs_qcom_ice_data ice;
 };
 
 #define ufs_qcom_is_link_off(hba) ufshcd_is_link_off(hba)
-- 
1.8.5.2

-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* RE: [PATCH v7 0/5] phy: qcom-ufs: add support for QUALCOMM
  2015-01-15 14:32 [PATCH v7 0/5] phy: qcom-ufs: add support for QUALCOMM Yaniv Gardi
@ 2015-01-15 15:03   ` Dov Levenglick
  2015-01-15 14:32   ` Yaniv Gardi
                     ` (4 subsequent siblings)
  5 siblings, 0 replies; 27+ messages in thread
From: Dov Levenglick @ 2015-01-15 15:03 UTC (permalink / raw)
  To: 'Yaniv Gardi', James.Bottomley, hch
  Cc: linux-kernel, linux-scsi, linux-arm-msm, santoshsy,
	linux-scsi-owner, subhashj, noag, draviv

Reviewed-by: Dov Levenglick <dovl@codeaurora.org>


Dov Levenglick

QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a
Linux Foundation Collaborative Project

> -----Original Message-----
> From: linux-scsi-owner@vger.kernel.org [mailto:linux-scsi-
> owner@vger.kernel.org] On Behalf Of Yaniv Gardi
> Sent: Thursday, January 15, 2015 4:33 PM
> To: James.Bottomley@HansenPartnership.com; hch@infradead.org
> Cc: linux-kernel@vger.kernel.org; linux-scsi@vger.kernel.org; linux-arm-
> msm@vger.kernel.org; santoshsy@gmail.com; linux-scsi-
> owner@vger.kernel.org; subhashj@codeaurora.org;
> ygardi@codeaurora.org; noag@codeaurora.org; draviv@codeaurora.org
> Subject: [PATCH v7 0/5] phy: qcom-ufs: add support for QUALCOMM
> 
> Changes from V6:
> - change#1 in the V6 series: 0001-phy-qcom-ufs-add-support-for-20nm-phy
>   has been divided into 2 patches:
>   A: phy: qcom-ufs: add support for QUALCOMM Technologies UFS PHY
> drivers
>   B: phy: qcom-ufs: add support for 20nm phy
>   in order to seperate the common code of the Qualcomm Technologies
>   UFS PHYs and the specific UFS PHYs that will be added.
> 
> Yaniv Gardi (5):
>   phy: qcom-ufs: add support for QUALCOMM Technologies UFS PHY drivers
>   phy: qcom-ufs: add support for 20nm phy
>   scsi: ufs-qcom: add support for Qualcomm Technologies Inc platforms
>   phy: qcom-ufs: add support for 14nm phy
>   scsi: ufs-qcom-ice: add Inline Crypto Engine (ICE) support for UFS
> 
>  drivers/phy/Kconfig                 |    7 +
>  drivers/phy/Makefile                |    3 +
>  drivers/phy/phy-qcom-ufs-i.h        |  159 ++++++
>  drivers/phy/phy-qcom-ufs-qmp-14nm.c |  201 +++++++
>  drivers/phy/phy-qcom-ufs-qmp-14nm.h |  177 ++++++
>  drivers/phy/phy-qcom-ufs-qmp-20nm.c |  257 +++++++++
>  drivers/phy/phy-qcom-ufs-qmp-20nm.h |  235 ++++++++
>  drivers/phy/phy-qcom-ufs.c          |  745 ++++++++++++++++++++++++
>  drivers/scsi/ufs/Kconfig            |   25 +
>  drivers/scsi/ufs/Makefile           |    2 +
>  drivers/scsi/ufs/ufs-qcom-ice.c     |  520 +++++++++++++++++
>  drivers/scsi/ufs/ufs-qcom-ice.h     |  113 ++++
>  drivers/scsi/ufs/ufs-qcom.c         | 1058
> +++++++++++++++++++++++++++++++++++
>  drivers/scsi/ufs/ufs-qcom.h         |  195 +++++++
>  include/linux/phy/phy-qcom-ufs.h    |   59 ++
>  15 files changed, 3756 insertions(+)
>  create mode 100644 drivers/phy/phy-qcom-ufs-i.h
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-14nm.c
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-14nm.h
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.c
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.h
>  create mode 100644 drivers/phy/phy-qcom-ufs.c
>  create mode 100644 drivers/scsi/ufs/ufs-qcom-ice.c
>  create mode 100644 drivers/scsi/ufs/ufs-qcom-ice.h
>  create mode 100644 drivers/scsi/ufs/ufs-qcom.c
>  create mode 100644 drivers/scsi/ufs/ufs-qcom.h
>  create mode 100644 include/linux/phy/phy-qcom-ufs.h
> 
> --
> 1.8.5.2
> 
> --
> QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a
> member of Code Aurora Forum, hosted by The Linux Foundation
> --
> To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 27+ messages in thread

* RE: [PATCH v7 0/5] phy: qcom-ufs: add support for QUALCOMM
@ 2015-01-15 15:03   ` Dov Levenglick
  0 siblings, 0 replies; 27+ messages in thread
From: Dov Levenglick @ 2015-01-15 15:03 UTC (permalink / raw)
  To: 'Yaniv Gardi', James.Bottomley, hch
  Cc: linux-kernel, linux-scsi, linux-arm-msm, santoshsy,
	linux-scsi-owner, subhashj, noag, draviv

Reviewed-by: Dov Levenglick <dovl@codeaurora.org>


Dov Levenglick

QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a
Linux Foundation Collaborative Project

> -----Original Message-----
> From: linux-scsi-owner@vger.kernel.org [mailto:linux-scsi-
> owner@vger.kernel.org] On Behalf Of Yaniv Gardi
> Sent: Thursday, January 15, 2015 4:33 PM
> To: James.Bottomley@HansenPartnership.com; hch@infradead.org
> Cc: linux-kernel@vger.kernel.org; linux-scsi@vger.kernel.org; linux-arm-
> msm@vger.kernel.org; santoshsy@gmail.com; linux-scsi-
> owner@vger.kernel.org; subhashj@codeaurora.org;
> ygardi@codeaurora.org; noag@codeaurora.org; draviv@codeaurora.org
> Subject: [PATCH v7 0/5] phy: qcom-ufs: add support for QUALCOMM
> 
> Changes from V6:
> - change#1 in the V6 series: 0001-phy-qcom-ufs-add-support-for-20nm-phy
>   has been divided into 2 patches:
>   A: phy: qcom-ufs: add support for QUALCOMM Technologies UFS PHY
> drivers
>   B: phy: qcom-ufs: add support for 20nm phy
>   in order to seperate the common code of the Qualcomm Technologies
>   UFS PHYs and the specific UFS PHYs that will be added.
> 
> Yaniv Gardi (5):
>   phy: qcom-ufs: add support for QUALCOMM Technologies UFS PHY drivers
>   phy: qcom-ufs: add support for 20nm phy
>   scsi: ufs-qcom: add support for Qualcomm Technologies Inc platforms
>   phy: qcom-ufs: add support for 14nm phy
>   scsi: ufs-qcom-ice: add Inline Crypto Engine (ICE) support for UFS
> 
>  drivers/phy/Kconfig                 |    7 +
>  drivers/phy/Makefile                |    3 +
>  drivers/phy/phy-qcom-ufs-i.h        |  159 ++++++
>  drivers/phy/phy-qcom-ufs-qmp-14nm.c |  201 +++++++
>  drivers/phy/phy-qcom-ufs-qmp-14nm.h |  177 ++++++
>  drivers/phy/phy-qcom-ufs-qmp-20nm.c |  257 +++++++++
>  drivers/phy/phy-qcom-ufs-qmp-20nm.h |  235 ++++++++
>  drivers/phy/phy-qcom-ufs.c          |  745 ++++++++++++++++++++++++
>  drivers/scsi/ufs/Kconfig            |   25 +
>  drivers/scsi/ufs/Makefile           |    2 +
>  drivers/scsi/ufs/ufs-qcom-ice.c     |  520 +++++++++++++++++
>  drivers/scsi/ufs/ufs-qcom-ice.h     |  113 ++++
>  drivers/scsi/ufs/ufs-qcom.c         | 1058
> +++++++++++++++++++++++++++++++++++
>  drivers/scsi/ufs/ufs-qcom.h         |  195 +++++++
>  include/linux/phy/phy-qcom-ufs.h    |   59 ++
>  15 files changed, 3756 insertions(+)
>  create mode 100644 drivers/phy/phy-qcom-ufs-i.h
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-14nm.c
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-14nm.h
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.c
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.h
>  create mode 100644 drivers/phy/phy-qcom-ufs.c
>  create mode 100644 drivers/scsi/ufs/ufs-qcom-ice.c
>  create mode 100644 drivers/scsi/ufs/ufs-qcom-ice.h
>  create mode 100644 drivers/scsi/ufs/ufs-qcom.c
>  create mode 100644 drivers/scsi/ufs/ufs-qcom.h
>  create mode 100644 include/linux/phy/phy-qcom-ufs.h
> 
> --
> 1.8.5.2
> 
> --
> QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a
> member of Code Aurora Forum, hosted by The Linux Foundation
> --
> To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v7 1/5] phy: qcom-ufs: add support for QUALCOMM Technologies UFS PHY drivers
  2015-01-15 14:32   ` Yaniv Gardi
@ 2015-01-15 15:17     ` dovl
  -1 siblings, 0 replies; 27+ messages in thread
From: dovl @ 2015-01-15 15:17 UTC (permalink / raw)
  Cc: james.bottomley, hch, linux-kernel, linux-scsi, linux-arm-msm,
	santoshsy, linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Kishon Vijay Abraham I, Grant Likely, Rob Herring,
	open list:OPEN FIRMWARE AND...

Reviewed-by: Dov Levenglick <dovl@codeaurora.org>

> This change adds a generic and common API support for ufs phy QUALCOMM
> Technologies. This support provides common code and also points
> to specific phy callbacks to differentiate between different behaviors
> of frequent use-cases (like power on, power off, phy calibration etc).
>
> Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>
>
> ---
>  drivers/phy/Kconfig          |   7 +
>  drivers/phy/Makefile         |   1 +
>  drivers/phy/phy-qcom-ufs-i.h | 118 +++++++
>  drivers/phy/phy-qcom-ufs.c   | 745
> +++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 871 insertions(+)
>  create mode 100644 drivers/phy/phy-qcom-ufs-i.h
>  create mode 100644 drivers/phy/phy-qcom-ufs.c
>
> diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
> index ccad880..26a7623 100644
> --- a/drivers/phy/Kconfig
> +++ b/drivers/phy/Kconfig
> @@ -277,4 +277,11 @@ config PHY_STIH41X_USB
>  	  Enable this to support the USB transceiver that is part of
>  	  STMicroelectronics STiH41x SoC series.
>
> +config PHY_QCOM_UFS
> +	tristate "Qualcomm UFS PHY driver"
> +	depends on OF && ARCH_MSM
> +	select GENERIC_PHY
> +	help
> +	  Support for UFS PHY on QCOM chipsets.
> +
>  endmenu
> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
> index aa74f96..335965d 100644
> --- a/drivers/phy/Makefile
> +++ b/drivers/phy/Makefile
> @@ -34,3 +34,4 @@ 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_STIH41X_USB)		+= phy-stih41x-usb.o
> +obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs.o
> diff --git a/drivers/phy/phy-qcom-ufs-i.h b/drivers/phy/phy-qcom-ufs-i.h
> new file mode 100644
> index 0000000..dac200f
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs-i.h
> @@ -0,0 +1,118 @@
> +/*
> + * Copyright (c) 2013-2015, 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.
> + *
> + */
> +
> +#ifndef UFS_QCOM_PHY_I_H_
> +#define UFS_QCOM_PHY_I_H_
> +
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/phy/phy.h>
> +#include <linux/platform_device.h>
> +#include <linux/io.h>
> +#include <linux/delay.h>
> +
> +#define UFS_QCOM_PHY_NAME_LEN	30
> +
> +struct ufs_qcom_phy_calibration {
> +	u32 reg_offset;
> +	u32 cfg_value;
> +};
> +
> +struct ufs_qcom_phy_vreg {
> +	const char *name;
> +	struct regulator *reg;
> +	int max_uA;
> +	int min_uV;
> +	int max_uV;
> +	bool enabled;
> +	bool is_always_on;
> +};
> +
> +struct ufs_qcom_phy {
> +	struct list_head list;
> +	struct device *dev;
> +	void __iomem *mmio;
> +	void __iomem *dev_ref_clk_ctrl_mmio;
> +	struct clk *tx_iface_clk;
> +	struct clk *rx_iface_clk;
> +	bool is_iface_clk_enabled;
> +	struct clk *ref_clk_src;
> +	struct clk *ref_clk_parent;
> +	struct clk *ref_clk;
> +	bool is_ref_clk_enabled;
> +	bool is_dev_ref_clk_enabled;
> +	struct ufs_qcom_phy_vreg vdda_pll;
> +	struct ufs_qcom_phy_vreg vdda_phy;
> +	struct ufs_qcom_phy_vreg vddp_ref_clk;
> +	unsigned int quirks;
> +
> +	/*
> +	* If UFS link is put into Hibern8 and if UFS PHY analog hardware
> is
> +	* power collapsed (by clearing UFS_PHY_POWER_DOWN_CONTROL),
> Hibern8
> +	* exit might fail even after powering on UFS PHY analog hardware.
> +	* Enabling this quirk will help to solve above issue by doing
> +	* custom PHY settings just before PHY analog power collapse.
> +	*/
> +	#define UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE
> BIT(0)
> +
> +	u8 host_ctrl_rev_major;
> +	u16 host_ctrl_rev_minor;
> +	u16 host_ctrl_rev_step;
> +
> +	char name[UFS_QCOM_PHY_NAME_LEN];
> +	struct ufs_qcom_phy_calibration *cached_regs;
> +	int cached_regs_table_size;
> +	bool is_powered_on;
> +	struct ufs_qcom_phy_specific_ops *phy_spec_ops;
> +};
> +
> +/**
> + * struct ufs_qcom_phy_specific_ops - set of pointers to functions which
> have a
> + * specific implementation per phy. Each UFS phy, should implement
> + * those functions according to its spec and requirements
> + * @calibrate_phy: pointer to a function that calibrate the phy
> + * @start_serdes: pointer to a function that starts the serdes
> + * @is_physical_coding_sublayer_ready: pointer to a function that
> + * checks pcs readiness. returns 0 for success and non-zero for error.
> + * @set_tx_lane_enable: pointer to a function that enable tx lanes
> + * @power_control: pointer to a function that controls analog rail of phy
> + * and writes to QSERDES_RX_SIGDET_CNTRL attribute
> + */
> +struct ufs_qcom_phy_specific_ops {
> +	int (*calibrate_phy)(struct ufs_qcom_phy *phy, bool is_rate_B);
> +	void (*start_serdes)(struct ufs_qcom_phy *phy);
> +	int (*is_physical_coding_sublayer_ready)(struct ufs_qcom_phy
> *phy);
> +	void (*set_tx_lane_enable)(struct ufs_qcom_phy *phy, u32 val);
> +	void (*power_control)(struct ufs_qcom_phy *phy, bool val);
> +};
> +
> +struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy);
> +int ufs_qcom_phy_power_on(struct phy *generic_phy);
> +int ufs_qcom_phy_power_off(struct phy *generic_phy);
> +int ufs_qcom_phy_exit(struct phy *generic_phy);
> +int ufs_qcom_phy_init_clks(struct phy *generic_phy,
> +			struct ufs_qcom_phy *phy_common);
> +int ufs_qcom_phy_init_vregulators(struct phy *generic_phy,
> +			struct ufs_qcom_phy *phy_common);
> +int ufs_qcom_phy_remove(struct phy *generic_phy,
> +		       struct ufs_qcom_phy *ufs_qcom_phy);
> +struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
> +			struct ufs_qcom_phy *common_cfg,
> +			struct phy_ops *ufs_qcom_phy_gen_ops,
> +			struct ufs_qcom_phy_specific_ops *phy_spec_ops);
> +int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
> +			struct ufs_qcom_phy_calibration *tbl_A, int
> tbl_size_A,
> +			struct ufs_qcom_phy_calibration *tbl_B, int
> tbl_size_B,
> +			bool is_rate_B);
> +#endif
> diff --git a/drivers/phy/phy-qcom-ufs.c b/drivers/phy/phy-qcom-ufs.c
> new file mode 100644
> index 0000000..44ee983
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs.c
> @@ -0,0 +1,745 @@
> +/*
> + * Copyright (c) 2013-2015, 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 "phy-qcom-ufs-i.h"
> +
> +#define MAX_PROP_NAME              32
> +#define VDDA_PHY_MIN_UV            1000000
> +#define VDDA_PHY_MAX_UV            1000000
> +#define VDDA_PLL_MIN_UV            1800000
> +#define VDDA_PLL_MAX_UV            1800000
> +#define VDDP_REF_CLK_MIN_UV        1200000
> +#define VDDP_REF_CLK_MAX_UV        1200000
> +
> +static int __ufs_qcom_phy_init_vreg(struct phy *, struct
> ufs_qcom_phy_vreg *,
> +				    const char *, bool);
> +static int ufs_qcom_phy_init_vreg(struct phy *, struct ufs_qcom_phy_vreg
> *,
> +				  const char *);
> +static int ufs_qcom_phy_base_init(struct platform_device *pdev,
> +				  struct ufs_qcom_phy *phy_common);
> +
> +int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
> +			   struct ufs_qcom_phy_calibration *tbl_A,
> +			   int tbl_size_A,
> +			   struct ufs_qcom_phy_calibration *tbl_B,
> +			   int tbl_size_B, bool is_rate_B)
> +{
> +	int i;
> +	int ret = 0;
> +
> +	if (!tbl_A) {
> +		dev_err(ufs_qcom_phy->dev, "%s: tbl_A is NULL", __func__);
> +		ret = EINVAL;
> +		goto out;
> +	}
> +
> +	for (i = 0; i < tbl_size_A; i++)
> +		writel_relaxed(tbl_A[i].cfg_value,
> +			       ufs_qcom_phy->mmio + tbl_A[i].reg_offset);
> +
> +	/*
> +	 * In case we would like to work in rate B, we need
> +	 * to override a registers that were configured in rate A table
> +	 * with registers of rate B table.
> +	 * table.
> +	 */
> +	if (is_rate_B) {
> +		if (!tbl_B) {
> +			dev_err(ufs_qcom_phy->dev, "%s: tbl_B is NULL",
> +				__func__);
> +			ret = EINVAL;
> +			goto out;
> +		}
> +
> +		for (i = 0; i < tbl_size_B; i++)
> +			writel_relaxed(tbl_B[i].cfg_value,
> +				ufs_qcom_phy->mmio + tbl_B[i].reg_offset);
> +	}
> +
> +	/* flush buffered writes */
> +	mb();
> +
> +out:
> +	return ret;
> +}
> +
> +struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
> +				struct ufs_qcom_phy *common_cfg,
> +				struct phy_ops *ufs_qcom_phy_gen_ops,
> +				struct ufs_qcom_phy_specific_ops
> *phy_spec_ops)
> +{
> +	int err;
> +	struct device *dev = &pdev->dev;
> +	struct phy *generic_phy = NULL;
> +	struct phy_provider *phy_provider;
> +
> +	err = ufs_qcom_phy_base_init(pdev, common_cfg);
> +	if (err) {
> +		dev_err(dev, "%s: phy base init failed %d\n", __func__,
> err);
> +		goto out;
> +	}
> +
> +	phy_provider = devm_of_phy_provider_register(dev,
> of_phy_simple_xlate);
> +	if (IS_ERR(phy_provider)) {
> +		err = PTR_ERR(phy_provider);
> +		dev_err(dev, "%s: failed to register phy %d\n", __func__,
> err);
> +		goto out;
> +	}
> +
> +	generic_phy = devm_phy_create(dev, NULL, ufs_qcom_phy_gen_ops);
> +	if (IS_ERR(generic_phy)) {
> +		err =  PTR_ERR(generic_phy);
> +		dev_err(dev, "%s: failed to create phy %d\n", __func__,
> err);
> +		goto out;
> +	}
> +
> +	common_cfg->phy_spec_ops = phy_spec_ops;
> +	common_cfg->dev = dev;
> +
> +out:
> +	return generic_phy;
> +}
> +
> +/*
> + * This assumes the embedded phy structure inside generic_phy is of type
> + * struct ufs_qcom_phy. In order to function properly it's crucial
> + * to keep the embedded struct "struct ufs_qcom_phy common_cfg"
> + * as the first inside generic_phy.
> + */
> +struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy)
> +{
> +	return (struct ufs_qcom_phy *)phy_get_drvdata(generic_phy);
> +}
> +
> +static
> +int ufs_qcom_phy_base_init(struct platform_device *pdev,
> +			   struct ufs_qcom_phy *phy_common)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct resource *res;
> +	int err = 0;
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> "phy_mem");
> +	if (!res) {
> +		dev_err(dev, "%s: phy_mem resource not found\n",
> __func__);
> +		err = -ENOMEM;
> +		goto out;
> +	}
> +
> +	phy_common->mmio = devm_ioremap_resource(dev, res);
> +	if (IS_ERR((void const *)phy_common->mmio)) {
> +		err = PTR_ERR((void const *)phy_common->mmio);
> +		phy_common->mmio = NULL;
> +		dev_err(dev, "%s: ioremap for phy_mem resource failed
> %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	/* "dev_ref_clk_ctrl_mem" is optional resource */
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> +					   "dev_ref_clk_ctrl_mem");
> +	if (!res) {
> +		dev_dbg(dev, "%s: dev_ref_clk_ctrl_mem resource not
> found\n",
> +			__func__);
> +		goto out;
> +	}
> +
> +	phy_common->dev_ref_clk_ctrl_mmio = devm_ioremap_resource(dev,
> res);
> +	if (IS_ERR((void const *)phy_common->dev_ref_clk_ctrl_mmio)) {
> +		err = PTR_ERR((void const
> *)phy_common->dev_ref_clk_ctrl_mmio);
> +		phy_common->dev_ref_clk_ctrl_mmio = NULL;
> +		dev_err(dev, "%s: ioremap for dev_ref_clk_ctrl_mem
> resource failed %d\n",
> +			__func__, err);
> +	}
> +
> +out:
> +	return err;
> +}
> +
> +static int __ufs_qcom_phy_clk_get(struct phy *phy,
> +			 const char *name, struct clk **clk_out, bool
> err_print)
> +{
> +	struct clk *clk;
> +	int err = 0;
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
> +	struct device *dev = ufs_qcom_phy->dev;
> +
> +	clk = devm_clk_get(dev, name);
> +	if (IS_ERR(clk)) {
> +		err = PTR_ERR(clk);
> +		if (err_print)
> +			dev_err(dev, "failed to get %s err %d", name,
> err);
> +	} else {
> +		*clk_out = clk;
> +	}
> +
> +	return err;
> +}
> +
> +static
> +int ufs_qcom_phy_clk_get(struct phy *phy,
> +			 const char *name, struct clk **clk_out)
> +{
> +	return __ufs_qcom_phy_clk_get(phy, name, clk_out, true);
> +}
> +
> +int
> +ufs_qcom_phy_init_clks(struct phy *generic_phy,
> +		       struct ufs_qcom_phy *phy_common)
> +{
> +	int err;
> +
> +	err = ufs_qcom_phy_clk_get(generic_phy, "tx_iface_clk",
> +				   &phy_common->tx_iface_clk);
> +	if (err)
> +		goto out;
> +
> +	err = ufs_qcom_phy_clk_get(generic_phy, "rx_iface_clk",
> +				   &phy_common->rx_iface_clk);
> +	if (err)
> +		goto out;
> +
> +	err = ufs_qcom_phy_clk_get(generic_phy, "ref_clk_src",
> +				   &phy_common->ref_clk_src);
> +	if (err)
> +		goto out;
> +
> +	/*
> +	 * "ref_clk_parent" is optional hence don't abort init if it's not
> +	 * found.
> +	 */
> +	__ufs_qcom_phy_clk_get(generic_phy, "ref_clk_parent",
> +				   &phy_common->ref_clk_parent, false);
> +
> +	err = ufs_qcom_phy_clk_get(generic_phy, "ref_clk",
> +				   &phy_common->ref_clk);
> +
> +out:
> +	return err;
> +}
> +
> +int
> +ufs_qcom_phy_init_vregulators(struct phy *generic_phy,
> +			      struct ufs_qcom_phy *phy_common)
> +{
> +	int err;
> +
> +	err = ufs_qcom_phy_init_vreg(generic_phy, &phy_common->vdda_pll,
> +		"vdda-pll");
> +	if (err)
> +		goto out;
> +
> +	err = ufs_qcom_phy_init_vreg(generic_phy, &phy_common->vdda_phy,
> +		"vdda-phy");
> +
> +	if (err)
> +		goto out;
> +
> +	/* vddp-ref-clk-* properties are optional */
> +	__ufs_qcom_phy_init_vreg(generic_phy, &phy_common->vddp_ref_clk,
> +				 "vddp-ref-clk", true);
> +out:
> +	return err;
> +}
> +
> +static int __ufs_qcom_phy_init_vreg(struct phy *phy,
> +		struct ufs_qcom_phy_vreg *vreg, const char *name, bool
> optional)
> +{
> +	int err = 0;
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
> +	struct device *dev = ufs_qcom_phy->dev;
> +
> +	char prop_name[MAX_PROP_NAME];
> +
> +	vreg->name = kstrdup(name, GFP_KERNEL);
> +	if (!vreg->name) {
> +		err = -ENOMEM;
> +		goto out;
> +	}
> +
> +	vreg->reg = devm_regulator_get(dev, name);
> +	if (IS_ERR(vreg->reg)) {
> +		err = PTR_ERR(vreg->reg);
> +		vreg->reg = NULL;
> +		if (!optional)
> +			dev_err(dev, "failed to get %s, %d\n", name, err);
> +		goto out;
> +	}
> +
> +	if (dev->of_node) {
> +		snprintf(prop_name, MAX_PROP_NAME, "%s-max-microamp",
> name);
> +		err = of_property_read_u32(dev->of_node,
> +					prop_name, &vreg->max_uA);
> +		if (err && err != -EINVAL) {
> +			dev_err(dev, "%s: failed to read %s\n",
> +					__func__, prop_name);
> +			goto out;
> +		} else if (err == -EINVAL || !vreg->max_uA) {
> +			if (regulator_count_voltages(vreg->reg) > 0) {
> +				dev_err(dev, "%s: %s is mandatory\n",
> +						__func__, prop_name);
> +				goto out;
> +			}
> +			err = 0;
> +		}
> +		snprintf(prop_name, MAX_PROP_NAME, "%s-always-on", name);
> +		if (of_get_property(dev->of_node, prop_name, NULL))
> +			vreg->is_always_on = true;
> +		else
> +			vreg->is_always_on = false;
> +	}
> +
> +	if (!strcmp(name, "vdda-pll")) {
> +		vreg->max_uV = VDDA_PLL_MAX_UV;
> +		vreg->min_uV = VDDA_PLL_MIN_UV;
> +	} else if (!strcmp(name, "vdda-phy")) {
> +		vreg->max_uV = VDDA_PHY_MAX_UV;
> +		vreg->min_uV = VDDA_PHY_MIN_UV;
> +	} else if (!strcmp(name, "vddp-ref-clk")) {
> +		vreg->max_uV = VDDP_REF_CLK_MAX_UV;
> +		vreg->min_uV = VDDP_REF_CLK_MIN_UV;
> +	}
> +
> +out:
> +	if (err)
> +		kfree(vreg->name);
> +	return err;
> +}
> +
> +static int ufs_qcom_phy_init_vreg(struct phy *phy,
> +			struct ufs_qcom_phy_vreg *vreg, const char *name)
> +{
> +	return __ufs_qcom_phy_init_vreg(phy, vreg, name, false);
> +}
> +
> +static
> +int ufs_qcom_phy_cfg_vreg(struct phy *phy,
> +			  struct ufs_qcom_phy_vreg *vreg, bool on)
> +{
> +	int ret = 0;
> +	struct regulator *reg = vreg->reg;
> +	const char *name = vreg->name;
> +	int min_uV;
> +	int uA_load;
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
> +	struct device *dev = ufs_qcom_phy->dev;
> +
> +	BUG_ON(!vreg);
> +
> +	if (regulator_count_voltages(reg) > 0) {
> +		min_uV = on ? vreg->min_uV : 0;
> +		ret = regulator_set_voltage(reg, min_uV, vreg->max_uV);
> +		if (ret) {
> +			dev_err(dev, "%s: %s set voltage failed,
> err=%d\n",
> +					__func__, name, ret);
> +			goto out;
> +		}
> +		uA_load = on ? vreg->max_uA : 0;
> +		ret = regulator_set_optimum_mode(reg, uA_load);
> +		if (ret >= 0) {
> +			/*
> +			 * regulator_set_optimum_mode() returns new
> regulator
> +			 * mode upon success.
> +			 */
> +			ret = 0;
> +		} else {
> +			dev_err(dev, "%s: %s set optimum mode(uA_load=%d)
> failed, err=%d\n",
> +					__func__, name, uA_load, ret);
> +			goto out;
> +		}
> +	}
> +out:
> +	return ret;
> +}
> +
> +static
> +int ufs_qcom_phy_enable_vreg(struct phy *phy,
> +			     struct ufs_qcom_phy_vreg *vreg)
> +{
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
> +	struct device *dev = ufs_qcom_phy->dev;
> +	int ret = 0;
> +
> +	if (!vreg || vreg->enabled)
> +		goto out;
> +
> +	ret = ufs_qcom_phy_cfg_vreg(phy, vreg, true);
> +	if (ret) {
> +		dev_err(dev, "%s: ufs_qcom_phy_cfg_vreg() failed,
> err=%d\n",
> +			__func__, ret);
> +		goto out;
> +	}
> +
> +	ret = regulator_enable(vreg->reg);
> +	if (ret) {
> +		dev_err(dev, "%s: enable failed, err=%d\n",
> +				__func__, ret);
> +		goto out;
> +	}
> +
> +	vreg->enabled = true;
> +out:
> +	return ret;
> +}
> +
> +int ufs_qcom_phy_enable_ref_clk(struct phy *generic_phy)
> +{
> +	int ret = 0;
> +	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
> +
> +	if (phy->is_ref_clk_enabled)
> +		goto out;
> +
> +	/*
> +	 * reference clock is propagated in a daisy-chained manner from
> +	 * source to phy, so ungate them at each stage.
> +	 */
> +	ret = clk_prepare_enable(phy->ref_clk_src);
> +	if (ret) {
> +		dev_err(phy->dev, "%s: ref_clk_src enable failed %d\n",
> +				__func__, ret);
> +		goto out;
> +	}
> +
> +	/*
> +	 * "ref_clk_parent" is optional clock hence make sure that clk
> reference
> +	 * is available before trying to enable the clock.
> +	 */
> +	if (phy->ref_clk_parent) {
> +		ret = clk_prepare_enable(phy->ref_clk_parent);
> +		if (ret) {
> +			dev_err(phy->dev, "%s: ref_clk_parent enable
> failed %d\n",
> +					__func__, ret);
> +			goto out_disable_src;
> +		}
> +	}
> +
> +	ret = clk_prepare_enable(phy->ref_clk);
> +	if (ret) {
> +		dev_err(phy->dev, "%s: ref_clk enable failed %d\n",
> +				__func__, ret);
> +		goto out_disable_parent;
> +	}
> +
> +	phy->is_ref_clk_enabled = true;
> +	goto out;
> +
> +out_disable_parent:
> +	if (phy->ref_clk_parent)
> +		clk_disable_unprepare(phy->ref_clk_parent);
> +out_disable_src:
> +	clk_disable_unprepare(phy->ref_clk_src);
> +out:
> +	return ret;
> +}
> +
> +static
> +int ufs_qcom_phy_disable_vreg(struct phy *phy,
> +			      struct ufs_qcom_phy_vreg *vreg)
> +{
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
> +	struct device *dev = ufs_qcom_phy->dev;
> +	int ret = 0;
> +
> +	if (!vreg || !vreg->enabled || vreg->is_always_on)
> +		goto out;
> +
> +	ret = regulator_disable(vreg->reg);
> +
> +	if (!ret) {
> +		/* ignore errors on applying disable config */
> +		ufs_qcom_phy_cfg_vreg(phy, vreg, false);
> +		vreg->enabled = false;
> +	} else {
> +		dev_err(dev, "%s: %s disable failed, err=%d\n",
> +				__func__, vreg->name, ret);
> +	}
> +out:
> +	return ret;
> +}
> +
> +void ufs_qcom_phy_disable_ref_clk(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
> +
> +	if (phy->is_ref_clk_enabled) {
> +		clk_disable_unprepare(phy->ref_clk);
> +		/*
> +		 * "ref_clk_parent" is optional clock hence make sure that
> clk
> +		 * reference is available before trying to disable the
> clock.
> +		 */
> +		if (phy->ref_clk_parent)
> +			clk_disable_unprepare(phy->ref_clk_parent);
> +		clk_disable_unprepare(phy->ref_clk_src);
> +		phy->is_ref_clk_enabled = false;
> +	}
> +}
> +
> +#define UFS_REF_CLK_EN	(1 << 5)
> +
> +static void ufs_qcom_phy_dev_ref_clk_ctrl(struct phy *generic_phy, bool
> enable)
> +{
> +	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
> +
> +	if (phy->dev_ref_clk_ctrl_mmio &&
> +	    (enable ^ phy->is_dev_ref_clk_enabled)) {
> +		u32 temp = readl_relaxed(phy->dev_ref_clk_ctrl_mmio);
> +
> +		if (enable)
> +			temp |= UFS_REF_CLK_EN;
> +		else
> +			temp &= ~UFS_REF_CLK_EN;
> +
> +		/*
> +		 * If we are here to disable this clock immediately after
> +		 * entering into hibern8, we need to make sure that device
> +		 * ref_clk is active atleast 1us after the hibern8 enter.
> +		 */
> +		if (!enable)
> +			udelay(1);
> +
> +		writel_relaxed(temp, phy->dev_ref_clk_ctrl_mmio);
> +		/* ensure that ref_clk is enabled/disabled before we
> return */
> +		wmb();
> +		/*
> +		 * If we call hibern8 exit after this, we need to make
> sure that
> +		 * device ref_clk is stable for atleast 1us before the
> hibern8
> +		 * exit command.
> +		 */
> +		if (enable)
> +			udelay(1);
> +
> +		phy->is_dev_ref_clk_enabled = enable;
> +	}
> +}
> +
> +void ufs_qcom_phy_enable_dev_ref_clk(struct phy *generic_phy)
> +{
> +	ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, true);
> +}
> +
> +void ufs_qcom_phy_disable_dev_ref_clk(struct phy *generic_phy)
> +{
> +	ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, false);
> +}
> +
> +/* Turn ON M-PHY RMMI interface clocks */
> +int ufs_qcom_phy_enable_iface_clk(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
> +	int ret = 0;
> +
> +	if (phy->is_iface_clk_enabled)
> +		goto out;
> +
> +	ret = clk_prepare_enable(phy->tx_iface_clk);
> +	if (ret) {
> +		dev_err(phy->dev, "%s: tx_iface_clk enable failed %d\n",
> +				__func__, ret);
> +		goto out;
> +	}
> +	ret = clk_prepare_enable(phy->rx_iface_clk);
> +	if (ret) {
> +		clk_disable_unprepare(phy->tx_iface_clk);
> +		dev_err(phy->dev, "%s: rx_iface_clk enable failed %d.
> disabling also tx_iface_clk\n",
> +				__func__, ret);
> +		goto out;
> +	}
> +	phy->is_iface_clk_enabled = true;
> +
> +out:
> +	return ret;
> +}
> +
> +/* Turn OFF M-PHY RMMI interface clocks */
> +void ufs_qcom_phy_disable_iface_clk(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
> +
> +	if (phy->is_iface_clk_enabled) {
> +		clk_disable_unprepare(phy->tx_iface_clk);
> +		clk_disable_unprepare(phy->rx_iface_clk);
> +		phy->is_iface_clk_enabled = false;
> +	}
> +}
> +
> +int ufs_qcom_phy_start_serdes(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +	int ret = 0;
> +
> +	if (!ufs_qcom_phy->phy_spec_ops->start_serdes) {
> +		dev_err(ufs_qcom_phy->dev, "%s: start_serdes() callback is
> not supported\n",
> +			__func__);
> +		ret = -ENOTSUPP;
> +	} else {
> +		ufs_qcom_phy->phy_spec_ops->start_serdes(ufs_qcom_phy);
> +	}
> +
> +	return ret;
> +}
> +
> +int ufs_qcom_phy_set_tx_lane_enable(struct phy *generic_phy, u32
> tx_lanes)
> +{
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +	int ret = 0;
> +
> +	if (!ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable) {
> +		dev_err(ufs_qcom_phy->dev, "%s: set_tx_lane_enable()
> callback is not supported\n",
> +			__func__);
> +		ret = -ENOTSUPP;
> +	} else {
> +
> ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable(ufs_qcom_phy,
> +							       tx_lanes);
> +	}
> +
> +	return ret;
> +}
> +
> +void ufs_qcom_phy_save_controller_version(struct phy *generic_phy,
> +					  u8 major, u16 minor, u16 step)
> +{
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +
> +	ufs_qcom_phy->host_ctrl_rev_major = major;
> +	ufs_qcom_phy->host_ctrl_rev_minor = minor;
> +	ufs_qcom_phy->host_ctrl_rev_step = step;
> +}
> +
> +int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy, bool is_rate_B)
> +{
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +	int ret = 0;
> +
> +	if (!ufs_qcom_phy->phy_spec_ops->calibrate_phy) {
> +		dev_err(ufs_qcom_phy->dev, "%s: calibrate_phy() callback
> is not supported\n",
> +			__func__);
> +		ret = -ENOTSUPP;
> +	} else {
> +		ret = ufs_qcom_phy->phy_spec_ops->
> +				calibrate_phy(ufs_qcom_phy, is_rate_B);
> +		if (ret)
> +			dev_err(ufs_qcom_phy->dev, "%s: calibrate_phy()
> failed %d\n",
> +				__func__, ret);
> +	}
> +
> +	return ret;
> +}
> +
> +int ufs_qcom_phy_remove(struct phy *generic_phy,
> +			struct ufs_qcom_phy *ufs_qcom_phy)
> +{
> +	phy_power_off(generic_phy);
> +
> +	kfree(ufs_qcom_phy->vdda_pll.name);
> +	kfree(ufs_qcom_phy->vdda_phy.name);
> +
> +	return 0;
> +}
> +
> +int ufs_qcom_phy_exit(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +
> +	if (ufs_qcom_phy->is_powered_on)
> +		phy_power_off(generic_phy);
> +
> +	return 0;
> +}
> +
> +int ufs_qcom_phy_is_pcs_ready(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +
> +	if
> (!ufs_qcom_phy->phy_spec_ops->is_physical_coding_sublayer_ready) {
> +		dev_err(ufs_qcom_phy->dev, "%s:
> is_physical_coding_sublayer_ready() callback is not supported\n",
> +			__func__);
> +		return -ENOTSUPP;
> +	}
> +
> +	return ufs_qcom_phy->phy_spec_ops->
> +			is_physical_coding_sublayer_ready(ufs_qcom_phy);
> +}
> +
> +int ufs_qcom_phy_power_on(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
> +	struct device *dev = phy_common->dev;
> +	int err;
> +
> +	err = ufs_qcom_phy_enable_vreg(generic_phy,
> &phy_common->vdda_phy);
> +	if (err) {
> +		dev_err(dev, "%s enable vdda_phy failed, err=%d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	phy_common->phy_spec_ops->power_control(phy_common, true);
> +
> +	/* vdda_pll also enables ref clock LDOs so enable it first */
> +	err = ufs_qcom_phy_enable_vreg(generic_phy,
> &phy_common->vdda_pll);
> +	if (err) {
> +		dev_err(dev, "%s enable vdda_pll failed, err=%d\n",
> +			__func__, err);
> +		goto out_disable_phy;
> +	}
> +
> +	err = ufs_qcom_phy_enable_ref_clk(generic_phy);
> +	if (err) {
> +		dev_err(dev, "%s enable phy ref clock failed, err=%d\n",
> +			__func__, err);
> +		goto out_disable_pll;
> +	}
> +
> +	/* enable device PHY ref_clk pad rail */
> +	if (phy_common->vddp_ref_clk.reg) {
> +		err = ufs_qcom_phy_enable_vreg(generic_phy,
> +					       &phy_common->vddp_ref_clk);
> +		if (err) {
> +			dev_err(dev, "%s enable vddp_ref_clk failed,
> err=%d\n",
> +				__func__, err);
> +			goto out_disable_ref_clk;
> +		}
> +	}
> +
> +	phy_common->is_powered_on = true;
> +	goto out;
> +
> +out_disable_ref_clk:
> +	ufs_qcom_phy_disable_ref_clk(generic_phy);
> +out_disable_pll:
> +	ufs_qcom_phy_disable_vreg(generic_phy, &phy_common->vdda_pll);
> +out_disable_phy:
> +	ufs_qcom_phy_disable_vreg(generic_phy, &phy_common->vdda_phy);
> +out:
> +	return err;
> +}
> +
> +int ufs_qcom_phy_power_off(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
> +
> +	phy_common->phy_spec_ops->power_control(phy_common, false);
> +
> +	if (phy_common->vddp_ref_clk.reg)
> +		ufs_qcom_phy_disable_vreg(generic_phy,
> +					  &phy_common->vddp_ref_clk);
> +	ufs_qcom_phy_disable_ref_clk(generic_phy);
> +
> +	ufs_qcom_phy_disable_vreg(generic_phy, &phy_common->vdda_pll);
> +	ufs_qcom_phy_disable_vreg(generic_phy, &phy_common->vdda_phy);
> +	phy_common->is_powered_on = false;
> +
> +	return 0;
> +}
> --
> 1.8.5.2
>
> --
> QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
> of Code Aurora Forum, hosted by The Linux Foundation
> --
> To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v7 1/5] phy: qcom-ufs: add support for QUALCOMM Technologies UFS PHY drivers
@ 2015-01-15 15:17     ` dovl
  0 siblings, 0 replies; 27+ messages in thread
From: dovl @ 2015-01-15 15:17 UTC (permalink / raw)
  To: Yaniv Gardi
  Cc: james.bottomley, hch, linux-kernel, linux-scsi, linux-arm-msm,
	santoshsy, linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Kishon Vijay Abraham I, Grant Likely, Rob Herring,
	open list:OPEN FIRMWARE AND...

Reviewed-by: Dov Levenglick <dovl@codeaurora.org>

> This change adds a generic and common API support for ufs phy QUALCOMM
> Technologies. This support provides common code and also points
> to specific phy callbacks to differentiate between different behaviors
> of frequent use-cases (like power on, power off, phy calibration etc).
>
> Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>
>
> ---
>  drivers/phy/Kconfig          |   7 +
>  drivers/phy/Makefile         |   1 +
>  drivers/phy/phy-qcom-ufs-i.h | 118 +++++++
>  drivers/phy/phy-qcom-ufs.c   | 745
> +++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 871 insertions(+)
>  create mode 100644 drivers/phy/phy-qcom-ufs-i.h
>  create mode 100644 drivers/phy/phy-qcom-ufs.c
>
> diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
> index ccad880..26a7623 100644
> --- a/drivers/phy/Kconfig
> +++ b/drivers/phy/Kconfig
> @@ -277,4 +277,11 @@ config PHY_STIH41X_USB
>  	  Enable this to support the USB transceiver that is part of
>  	  STMicroelectronics STiH41x SoC series.
>
> +config PHY_QCOM_UFS
> +	tristate "Qualcomm UFS PHY driver"
> +	depends on OF && ARCH_MSM
> +	select GENERIC_PHY
> +	help
> +	  Support for UFS PHY on QCOM chipsets.
> +
>  endmenu
> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
> index aa74f96..335965d 100644
> --- a/drivers/phy/Makefile
> +++ b/drivers/phy/Makefile
> @@ -34,3 +34,4 @@ 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_STIH41X_USB)		+= phy-stih41x-usb.o
> +obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs.o
> diff --git a/drivers/phy/phy-qcom-ufs-i.h b/drivers/phy/phy-qcom-ufs-i.h
> new file mode 100644
> index 0000000..dac200f
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs-i.h
> @@ -0,0 +1,118 @@
> +/*
> + * Copyright (c) 2013-2015, 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.
> + *
> + */
> +
> +#ifndef UFS_QCOM_PHY_I_H_
> +#define UFS_QCOM_PHY_I_H_
> +
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/phy/phy.h>
> +#include <linux/platform_device.h>
> +#include <linux/io.h>
> +#include <linux/delay.h>
> +
> +#define UFS_QCOM_PHY_NAME_LEN	30
> +
> +struct ufs_qcom_phy_calibration {
> +	u32 reg_offset;
> +	u32 cfg_value;
> +};
> +
> +struct ufs_qcom_phy_vreg {
> +	const char *name;
> +	struct regulator *reg;
> +	int max_uA;
> +	int min_uV;
> +	int max_uV;
> +	bool enabled;
> +	bool is_always_on;
> +};
> +
> +struct ufs_qcom_phy {
> +	struct list_head list;
> +	struct device *dev;
> +	void __iomem *mmio;
> +	void __iomem *dev_ref_clk_ctrl_mmio;
> +	struct clk *tx_iface_clk;
> +	struct clk *rx_iface_clk;
> +	bool is_iface_clk_enabled;
> +	struct clk *ref_clk_src;
> +	struct clk *ref_clk_parent;
> +	struct clk *ref_clk;
> +	bool is_ref_clk_enabled;
> +	bool is_dev_ref_clk_enabled;
> +	struct ufs_qcom_phy_vreg vdda_pll;
> +	struct ufs_qcom_phy_vreg vdda_phy;
> +	struct ufs_qcom_phy_vreg vddp_ref_clk;
> +	unsigned int quirks;
> +
> +	/*
> +	* If UFS link is put into Hibern8 and if UFS PHY analog hardware
> is
> +	* power collapsed (by clearing UFS_PHY_POWER_DOWN_CONTROL),
> Hibern8
> +	* exit might fail even after powering on UFS PHY analog hardware.
> +	* Enabling this quirk will help to solve above issue by doing
> +	* custom PHY settings just before PHY analog power collapse.
> +	*/
> +	#define UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE
> BIT(0)
> +
> +	u8 host_ctrl_rev_major;
> +	u16 host_ctrl_rev_minor;
> +	u16 host_ctrl_rev_step;
> +
> +	char name[UFS_QCOM_PHY_NAME_LEN];
> +	struct ufs_qcom_phy_calibration *cached_regs;
> +	int cached_regs_table_size;
> +	bool is_powered_on;
> +	struct ufs_qcom_phy_specific_ops *phy_spec_ops;
> +};
> +
> +/**
> + * struct ufs_qcom_phy_specific_ops - set of pointers to functions which
> have a
> + * specific implementation per phy. Each UFS phy, should implement
> + * those functions according to its spec and requirements
> + * @calibrate_phy: pointer to a function that calibrate the phy
> + * @start_serdes: pointer to a function that starts the serdes
> + * @is_physical_coding_sublayer_ready: pointer to a function that
> + * checks pcs readiness. returns 0 for success and non-zero for error.
> + * @set_tx_lane_enable: pointer to a function that enable tx lanes
> + * @power_control: pointer to a function that controls analog rail of phy
> + * and writes to QSERDES_RX_SIGDET_CNTRL attribute
> + */
> +struct ufs_qcom_phy_specific_ops {
> +	int (*calibrate_phy)(struct ufs_qcom_phy *phy, bool is_rate_B);
> +	void (*start_serdes)(struct ufs_qcom_phy *phy);
> +	int (*is_physical_coding_sublayer_ready)(struct ufs_qcom_phy
> *phy);
> +	void (*set_tx_lane_enable)(struct ufs_qcom_phy *phy, u32 val);
> +	void (*power_control)(struct ufs_qcom_phy *phy, bool val);
> +};
> +
> +struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy);
> +int ufs_qcom_phy_power_on(struct phy *generic_phy);
> +int ufs_qcom_phy_power_off(struct phy *generic_phy);
> +int ufs_qcom_phy_exit(struct phy *generic_phy);
> +int ufs_qcom_phy_init_clks(struct phy *generic_phy,
> +			struct ufs_qcom_phy *phy_common);
> +int ufs_qcom_phy_init_vregulators(struct phy *generic_phy,
> +			struct ufs_qcom_phy *phy_common);
> +int ufs_qcom_phy_remove(struct phy *generic_phy,
> +		       struct ufs_qcom_phy *ufs_qcom_phy);
> +struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
> +			struct ufs_qcom_phy *common_cfg,
> +			struct phy_ops *ufs_qcom_phy_gen_ops,
> +			struct ufs_qcom_phy_specific_ops *phy_spec_ops);
> +int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
> +			struct ufs_qcom_phy_calibration *tbl_A, int
> tbl_size_A,
> +			struct ufs_qcom_phy_calibration *tbl_B, int
> tbl_size_B,
> +			bool is_rate_B);
> +#endif
> diff --git a/drivers/phy/phy-qcom-ufs.c b/drivers/phy/phy-qcom-ufs.c
> new file mode 100644
> index 0000000..44ee983
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs.c
> @@ -0,0 +1,745 @@
> +/*
> + * Copyright (c) 2013-2015, 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 "phy-qcom-ufs-i.h"
> +
> +#define MAX_PROP_NAME              32
> +#define VDDA_PHY_MIN_UV            1000000
> +#define VDDA_PHY_MAX_UV            1000000
> +#define VDDA_PLL_MIN_UV            1800000
> +#define VDDA_PLL_MAX_UV            1800000
> +#define VDDP_REF_CLK_MIN_UV        1200000
> +#define VDDP_REF_CLK_MAX_UV        1200000
> +
> +static int __ufs_qcom_phy_init_vreg(struct phy *, struct
> ufs_qcom_phy_vreg *,
> +				    const char *, bool);
> +static int ufs_qcom_phy_init_vreg(struct phy *, struct ufs_qcom_phy_vreg
> *,
> +				  const char *);
> +static int ufs_qcom_phy_base_init(struct platform_device *pdev,
> +				  struct ufs_qcom_phy *phy_common);
> +
> +int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
> +			   struct ufs_qcom_phy_calibration *tbl_A,
> +			   int tbl_size_A,
> +			   struct ufs_qcom_phy_calibration *tbl_B,
> +			   int tbl_size_B, bool is_rate_B)
> +{
> +	int i;
> +	int ret = 0;
> +
> +	if (!tbl_A) {
> +		dev_err(ufs_qcom_phy->dev, "%s: tbl_A is NULL", __func__);
> +		ret = EINVAL;
> +		goto out;
> +	}
> +
> +	for (i = 0; i < tbl_size_A; i++)
> +		writel_relaxed(tbl_A[i].cfg_value,
> +			       ufs_qcom_phy->mmio + tbl_A[i].reg_offset);
> +
> +	/*
> +	 * In case we would like to work in rate B, we need
> +	 * to override a registers that were configured in rate A table
> +	 * with registers of rate B table.
> +	 * table.
> +	 */
> +	if (is_rate_B) {
> +		if (!tbl_B) {
> +			dev_err(ufs_qcom_phy->dev, "%s: tbl_B is NULL",
> +				__func__);
> +			ret = EINVAL;
> +			goto out;
> +		}
> +
> +		for (i = 0; i < tbl_size_B; i++)
> +			writel_relaxed(tbl_B[i].cfg_value,
> +				ufs_qcom_phy->mmio + tbl_B[i].reg_offset);
> +	}
> +
> +	/* flush buffered writes */
> +	mb();
> +
> +out:
> +	return ret;
> +}
> +
> +struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
> +				struct ufs_qcom_phy *common_cfg,
> +				struct phy_ops *ufs_qcom_phy_gen_ops,
> +				struct ufs_qcom_phy_specific_ops
> *phy_spec_ops)
> +{
> +	int err;
> +	struct device *dev = &pdev->dev;
> +	struct phy *generic_phy = NULL;
> +	struct phy_provider *phy_provider;
> +
> +	err = ufs_qcom_phy_base_init(pdev, common_cfg);
> +	if (err) {
> +		dev_err(dev, "%s: phy base init failed %d\n", __func__,
> err);
> +		goto out;
> +	}
> +
> +	phy_provider = devm_of_phy_provider_register(dev,
> of_phy_simple_xlate);
> +	if (IS_ERR(phy_provider)) {
> +		err = PTR_ERR(phy_provider);
> +		dev_err(dev, "%s: failed to register phy %d\n", __func__,
> err);
> +		goto out;
> +	}
> +
> +	generic_phy = devm_phy_create(dev, NULL, ufs_qcom_phy_gen_ops);
> +	if (IS_ERR(generic_phy)) {
> +		err =  PTR_ERR(generic_phy);
> +		dev_err(dev, "%s: failed to create phy %d\n", __func__,
> err);
> +		goto out;
> +	}
> +
> +	common_cfg->phy_spec_ops = phy_spec_ops;
> +	common_cfg->dev = dev;
> +
> +out:
> +	return generic_phy;
> +}
> +
> +/*
> + * This assumes the embedded phy structure inside generic_phy is of type
> + * struct ufs_qcom_phy. In order to function properly it's crucial
> + * to keep the embedded struct "struct ufs_qcom_phy common_cfg"
> + * as the first inside generic_phy.
> + */
> +struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy)
> +{
> +	return (struct ufs_qcom_phy *)phy_get_drvdata(generic_phy);
> +}
> +
> +static
> +int ufs_qcom_phy_base_init(struct platform_device *pdev,
> +			   struct ufs_qcom_phy *phy_common)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct resource *res;
> +	int err = 0;
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> "phy_mem");
> +	if (!res) {
> +		dev_err(dev, "%s: phy_mem resource not found\n",
> __func__);
> +		err = -ENOMEM;
> +		goto out;
> +	}
> +
> +	phy_common->mmio = devm_ioremap_resource(dev, res);
> +	if (IS_ERR((void const *)phy_common->mmio)) {
> +		err = PTR_ERR((void const *)phy_common->mmio);
> +		phy_common->mmio = NULL;
> +		dev_err(dev, "%s: ioremap for phy_mem resource failed
> %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	/* "dev_ref_clk_ctrl_mem" is optional resource */
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> +					   "dev_ref_clk_ctrl_mem");
> +	if (!res) {
> +		dev_dbg(dev, "%s: dev_ref_clk_ctrl_mem resource not
> found\n",
> +			__func__);
> +		goto out;
> +	}
> +
> +	phy_common->dev_ref_clk_ctrl_mmio = devm_ioremap_resource(dev,
> res);
> +	if (IS_ERR((void const *)phy_common->dev_ref_clk_ctrl_mmio)) {
> +		err = PTR_ERR((void const
> *)phy_common->dev_ref_clk_ctrl_mmio);
> +		phy_common->dev_ref_clk_ctrl_mmio = NULL;
> +		dev_err(dev, "%s: ioremap for dev_ref_clk_ctrl_mem
> resource failed %d\n",
> +			__func__, err);
> +	}
> +
> +out:
> +	return err;
> +}
> +
> +static int __ufs_qcom_phy_clk_get(struct phy *phy,
> +			 const char *name, struct clk **clk_out, bool
> err_print)
> +{
> +	struct clk *clk;
> +	int err = 0;
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
> +	struct device *dev = ufs_qcom_phy->dev;
> +
> +	clk = devm_clk_get(dev, name);
> +	if (IS_ERR(clk)) {
> +		err = PTR_ERR(clk);
> +		if (err_print)
> +			dev_err(dev, "failed to get %s err %d", name,
> err);
> +	} else {
> +		*clk_out = clk;
> +	}
> +
> +	return err;
> +}
> +
> +static
> +int ufs_qcom_phy_clk_get(struct phy *phy,
> +			 const char *name, struct clk **clk_out)
> +{
> +	return __ufs_qcom_phy_clk_get(phy, name, clk_out, true);
> +}
> +
> +int
> +ufs_qcom_phy_init_clks(struct phy *generic_phy,
> +		       struct ufs_qcom_phy *phy_common)
> +{
> +	int err;
> +
> +	err = ufs_qcom_phy_clk_get(generic_phy, "tx_iface_clk",
> +				   &phy_common->tx_iface_clk);
> +	if (err)
> +		goto out;
> +
> +	err = ufs_qcom_phy_clk_get(generic_phy, "rx_iface_clk",
> +				   &phy_common->rx_iface_clk);
> +	if (err)
> +		goto out;
> +
> +	err = ufs_qcom_phy_clk_get(generic_phy, "ref_clk_src",
> +				   &phy_common->ref_clk_src);
> +	if (err)
> +		goto out;
> +
> +	/*
> +	 * "ref_clk_parent" is optional hence don't abort init if it's not
> +	 * found.
> +	 */
> +	__ufs_qcom_phy_clk_get(generic_phy, "ref_clk_parent",
> +				   &phy_common->ref_clk_parent, false);
> +
> +	err = ufs_qcom_phy_clk_get(generic_phy, "ref_clk",
> +				   &phy_common->ref_clk);
> +
> +out:
> +	return err;
> +}
> +
> +int
> +ufs_qcom_phy_init_vregulators(struct phy *generic_phy,
> +			      struct ufs_qcom_phy *phy_common)
> +{
> +	int err;
> +
> +	err = ufs_qcom_phy_init_vreg(generic_phy, &phy_common->vdda_pll,
> +		"vdda-pll");
> +	if (err)
> +		goto out;
> +
> +	err = ufs_qcom_phy_init_vreg(generic_phy, &phy_common->vdda_phy,
> +		"vdda-phy");
> +
> +	if (err)
> +		goto out;
> +
> +	/* vddp-ref-clk-* properties are optional */
> +	__ufs_qcom_phy_init_vreg(generic_phy, &phy_common->vddp_ref_clk,
> +				 "vddp-ref-clk", true);
> +out:
> +	return err;
> +}
> +
> +static int __ufs_qcom_phy_init_vreg(struct phy *phy,
> +		struct ufs_qcom_phy_vreg *vreg, const char *name, bool
> optional)
> +{
> +	int err = 0;
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
> +	struct device *dev = ufs_qcom_phy->dev;
> +
> +	char prop_name[MAX_PROP_NAME];
> +
> +	vreg->name = kstrdup(name, GFP_KERNEL);
> +	if (!vreg->name) {
> +		err = -ENOMEM;
> +		goto out;
> +	}
> +
> +	vreg->reg = devm_regulator_get(dev, name);
> +	if (IS_ERR(vreg->reg)) {
> +		err = PTR_ERR(vreg->reg);
> +		vreg->reg = NULL;
> +		if (!optional)
> +			dev_err(dev, "failed to get %s, %d\n", name, err);
> +		goto out;
> +	}
> +
> +	if (dev->of_node) {
> +		snprintf(prop_name, MAX_PROP_NAME, "%s-max-microamp",
> name);
> +		err = of_property_read_u32(dev->of_node,
> +					prop_name, &vreg->max_uA);
> +		if (err && err != -EINVAL) {
> +			dev_err(dev, "%s: failed to read %s\n",
> +					__func__, prop_name);
> +			goto out;
> +		} else if (err == -EINVAL || !vreg->max_uA) {
> +			if (regulator_count_voltages(vreg->reg) > 0) {
> +				dev_err(dev, "%s: %s is mandatory\n",
> +						__func__, prop_name);
> +				goto out;
> +			}
> +			err = 0;
> +		}
> +		snprintf(prop_name, MAX_PROP_NAME, "%s-always-on", name);
> +		if (of_get_property(dev->of_node, prop_name, NULL))
> +			vreg->is_always_on = true;
> +		else
> +			vreg->is_always_on = false;
> +	}
> +
> +	if (!strcmp(name, "vdda-pll")) {
> +		vreg->max_uV = VDDA_PLL_MAX_UV;
> +		vreg->min_uV = VDDA_PLL_MIN_UV;
> +	} else if (!strcmp(name, "vdda-phy")) {
> +		vreg->max_uV = VDDA_PHY_MAX_UV;
> +		vreg->min_uV = VDDA_PHY_MIN_UV;
> +	} else if (!strcmp(name, "vddp-ref-clk")) {
> +		vreg->max_uV = VDDP_REF_CLK_MAX_UV;
> +		vreg->min_uV = VDDP_REF_CLK_MIN_UV;
> +	}
> +
> +out:
> +	if (err)
> +		kfree(vreg->name);
> +	return err;
> +}
> +
> +static int ufs_qcom_phy_init_vreg(struct phy *phy,
> +			struct ufs_qcom_phy_vreg *vreg, const char *name)
> +{
> +	return __ufs_qcom_phy_init_vreg(phy, vreg, name, false);
> +}
> +
> +static
> +int ufs_qcom_phy_cfg_vreg(struct phy *phy,
> +			  struct ufs_qcom_phy_vreg *vreg, bool on)
> +{
> +	int ret = 0;
> +	struct regulator *reg = vreg->reg;
> +	const char *name = vreg->name;
> +	int min_uV;
> +	int uA_load;
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
> +	struct device *dev = ufs_qcom_phy->dev;
> +
> +	BUG_ON(!vreg);
> +
> +	if (regulator_count_voltages(reg) > 0) {
> +		min_uV = on ? vreg->min_uV : 0;
> +		ret = regulator_set_voltage(reg, min_uV, vreg->max_uV);
> +		if (ret) {
> +			dev_err(dev, "%s: %s set voltage failed,
> err=%d\n",
> +					__func__, name, ret);
> +			goto out;
> +		}
> +		uA_load = on ? vreg->max_uA : 0;
> +		ret = regulator_set_optimum_mode(reg, uA_load);
> +		if (ret >= 0) {
> +			/*
> +			 * regulator_set_optimum_mode() returns new
> regulator
> +			 * mode upon success.
> +			 */
> +			ret = 0;
> +		} else {
> +			dev_err(dev, "%s: %s set optimum mode(uA_load=%d)
> failed, err=%d\n",
> +					__func__, name, uA_load, ret);
> +			goto out;
> +		}
> +	}
> +out:
> +	return ret;
> +}
> +
> +static
> +int ufs_qcom_phy_enable_vreg(struct phy *phy,
> +			     struct ufs_qcom_phy_vreg *vreg)
> +{
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
> +	struct device *dev = ufs_qcom_phy->dev;
> +	int ret = 0;
> +
> +	if (!vreg || vreg->enabled)
> +		goto out;
> +
> +	ret = ufs_qcom_phy_cfg_vreg(phy, vreg, true);
> +	if (ret) {
> +		dev_err(dev, "%s: ufs_qcom_phy_cfg_vreg() failed,
> err=%d\n",
> +			__func__, ret);
> +		goto out;
> +	}
> +
> +	ret = regulator_enable(vreg->reg);
> +	if (ret) {
> +		dev_err(dev, "%s: enable failed, err=%d\n",
> +				__func__, ret);
> +		goto out;
> +	}
> +
> +	vreg->enabled = true;
> +out:
> +	return ret;
> +}
> +
> +int ufs_qcom_phy_enable_ref_clk(struct phy *generic_phy)
> +{
> +	int ret = 0;
> +	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
> +
> +	if (phy->is_ref_clk_enabled)
> +		goto out;
> +
> +	/*
> +	 * reference clock is propagated in a daisy-chained manner from
> +	 * source to phy, so ungate them at each stage.
> +	 */
> +	ret = clk_prepare_enable(phy->ref_clk_src);
> +	if (ret) {
> +		dev_err(phy->dev, "%s: ref_clk_src enable failed %d\n",
> +				__func__, ret);
> +		goto out;
> +	}
> +
> +	/*
> +	 * "ref_clk_parent" is optional clock hence make sure that clk
> reference
> +	 * is available before trying to enable the clock.
> +	 */
> +	if (phy->ref_clk_parent) {
> +		ret = clk_prepare_enable(phy->ref_clk_parent);
> +		if (ret) {
> +			dev_err(phy->dev, "%s: ref_clk_parent enable
> failed %d\n",
> +					__func__, ret);
> +			goto out_disable_src;
> +		}
> +	}
> +
> +	ret = clk_prepare_enable(phy->ref_clk);
> +	if (ret) {
> +		dev_err(phy->dev, "%s: ref_clk enable failed %d\n",
> +				__func__, ret);
> +		goto out_disable_parent;
> +	}
> +
> +	phy->is_ref_clk_enabled = true;
> +	goto out;
> +
> +out_disable_parent:
> +	if (phy->ref_clk_parent)
> +		clk_disable_unprepare(phy->ref_clk_parent);
> +out_disable_src:
> +	clk_disable_unprepare(phy->ref_clk_src);
> +out:
> +	return ret;
> +}
> +
> +static
> +int ufs_qcom_phy_disable_vreg(struct phy *phy,
> +			      struct ufs_qcom_phy_vreg *vreg)
> +{
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
> +	struct device *dev = ufs_qcom_phy->dev;
> +	int ret = 0;
> +
> +	if (!vreg || !vreg->enabled || vreg->is_always_on)
> +		goto out;
> +
> +	ret = regulator_disable(vreg->reg);
> +
> +	if (!ret) {
> +		/* ignore errors on applying disable config */
> +		ufs_qcom_phy_cfg_vreg(phy, vreg, false);
> +		vreg->enabled = false;
> +	} else {
> +		dev_err(dev, "%s: %s disable failed, err=%d\n",
> +				__func__, vreg->name, ret);
> +	}
> +out:
> +	return ret;
> +}
> +
> +void ufs_qcom_phy_disable_ref_clk(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
> +
> +	if (phy->is_ref_clk_enabled) {
> +		clk_disable_unprepare(phy->ref_clk);
> +		/*
> +		 * "ref_clk_parent" is optional clock hence make sure that
> clk
> +		 * reference is available before trying to disable the
> clock.
> +		 */
> +		if (phy->ref_clk_parent)
> +			clk_disable_unprepare(phy->ref_clk_parent);
> +		clk_disable_unprepare(phy->ref_clk_src);
> +		phy->is_ref_clk_enabled = false;
> +	}
> +}
> +
> +#define UFS_REF_CLK_EN	(1 << 5)
> +
> +static void ufs_qcom_phy_dev_ref_clk_ctrl(struct phy *generic_phy, bool
> enable)
> +{
> +	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
> +
> +	if (phy->dev_ref_clk_ctrl_mmio &&
> +	    (enable ^ phy->is_dev_ref_clk_enabled)) {
> +		u32 temp = readl_relaxed(phy->dev_ref_clk_ctrl_mmio);
> +
> +		if (enable)
> +			temp |= UFS_REF_CLK_EN;
> +		else
> +			temp &= ~UFS_REF_CLK_EN;
> +
> +		/*
> +		 * If we are here to disable this clock immediately after
> +		 * entering into hibern8, we need to make sure that device
> +		 * ref_clk is active atleast 1us after the hibern8 enter.
> +		 */
> +		if (!enable)
> +			udelay(1);
> +
> +		writel_relaxed(temp, phy->dev_ref_clk_ctrl_mmio);
> +		/* ensure that ref_clk is enabled/disabled before we
> return */
> +		wmb();
> +		/*
> +		 * If we call hibern8 exit after this, we need to make
> sure that
> +		 * device ref_clk is stable for atleast 1us before the
> hibern8
> +		 * exit command.
> +		 */
> +		if (enable)
> +			udelay(1);
> +
> +		phy->is_dev_ref_clk_enabled = enable;
> +	}
> +}
> +
> +void ufs_qcom_phy_enable_dev_ref_clk(struct phy *generic_phy)
> +{
> +	ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, true);
> +}
> +
> +void ufs_qcom_phy_disable_dev_ref_clk(struct phy *generic_phy)
> +{
> +	ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, false);
> +}
> +
> +/* Turn ON M-PHY RMMI interface clocks */
> +int ufs_qcom_phy_enable_iface_clk(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
> +	int ret = 0;
> +
> +	if (phy->is_iface_clk_enabled)
> +		goto out;
> +
> +	ret = clk_prepare_enable(phy->tx_iface_clk);
> +	if (ret) {
> +		dev_err(phy->dev, "%s: tx_iface_clk enable failed %d\n",
> +				__func__, ret);
> +		goto out;
> +	}
> +	ret = clk_prepare_enable(phy->rx_iface_clk);
> +	if (ret) {
> +		clk_disable_unprepare(phy->tx_iface_clk);
> +		dev_err(phy->dev, "%s: rx_iface_clk enable failed %d.
> disabling also tx_iface_clk\n",
> +				__func__, ret);
> +		goto out;
> +	}
> +	phy->is_iface_clk_enabled = true;
> +
> +out:
> +	return ret;
> +}
> +
> +/* Turn OFF M-PHY RMMI interface clocks */
> +void ufs_qcom_phy_disable_iface_clk(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
> +
> +	if (phy->is_iface_clk_enabled) {
> +		clk_disable_unprepare(phy->tx_iface_clk);
> +		clk_disable_unprepare(phy->rx_iface_clk);
> +		phy->is_iface_clk_enabled = false;
> +	}
> +}
> +
> +int ufs_qcom_phy_start_serdes(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +	int ret = 0;
> +
> +	if (!ufs_qcom_phy->phy_spec_ops->start_serdes) {
> +		dev_err(ufs_qcom_phy->dev, "%s: start_serdes() callback is
> not supported\n",
> +			__func__);
> +		ret = -ENOTSUPP;
> +	} else {
> +		ufs_qcom_phy->phy_spec_ops->start_serdes(ufs_qcom_phy);
> +	}
> +
> +	return ret;
> +}
> +
> +int ufs_qcom_phy_set_tx_lane_enable(struct phy *generic_phy, u32
> tx_lanes)
> +{
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +	int ret = 0;
> +
> +	if (!ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable) {
> +		dev_err(ufs_qcom_phy->dev, "%s: set_tx_lane_enable()
> callback is not supported\n",
> +			__func__);
> +		ret = -ENOTSUPP;
> +	} else {
> +
> ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable(ufs_qcom_phy,
> +							       tx_lanes);
> +	}
> +
> +	return ret;
> +}
> +
> +void ufs_qcom_phy_save_controller_version(struct phy *generic_phy,
> +					  u8 major, u16 minor, u16 step)
> +{
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +
> +	ufs_qcom_phy->host_ctrl_rev_major = major;
> +	ufs_qcom_phy->host_ctrl_rev_minor = minor;
> +	ufs_qcom_phy->host_ctrl_rev_step = step;
> +}
> +
> +int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy, bool is_rate_B)
> +{
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +	int ret = 0;
> +
> +	if (!ufs_qcom_phy->phy_spec_ops->calibrate_phy) {
> +		dev_err(ufs_qcom_phy->dev, "%s: calibrate_phy() callback
> is not supported\n",
> +			__func__);
> +		ret = -ENOTSUPP;
> +	} else {
> +		ret = ufs_qcom_phy->phy_spec_ops->
> +				calibrate_phy(ufs_qcom_phy, is_rate_B);
> +		if (ret)
> +			dev_err(ufs_qcom_phy->dev, "%s: calibrate_phy()
> failed %d\n",
> +				__func__, ret);
> +	}
> +
> +	return ret;
> +}
> +
> +int ufs_qcom_phy_remove(struct phy *generic_phy,
> +			struct ufs_qcom_phy *ufs_qcom_phy)
> +{
> +	phy_power_off(generic_phy);
> +
> +	kfree(ufs_qcom_phy->vdda_pll.name);
> +	kfree(ufs_qcom_phy->vdda_phy.name);
> +
> +	return 0;
> +}
> +
> +int ufs_qcom_phy_exit(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +
> +	if (ufs_qcom_phy->is_powered_on)
> +		phy_power_off(generic_phy);
> +
> +	return 0;
> +}
> +
> +int ufs_qcom_phy_is_pcs_ready(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +
> +	if
> (!ufs_qcom_phy->phy_spec_ops->is_physical_coding_sublayer_ready) {
> +		dev_err(ufs_qcom_phy->dev, "%s:
> is_physical_coding_sublayer_ready() callback is not supported\n",
> +			__func__);
> +		return -ENOTSUPP;
> +	}
> +
> +	return ufs_qcom_phy->phy_spec_ops->
> +			is_physical_coding_sublayer_ready(ufs_qcom_phy);
> +}
> +
> +int ufs_qcom_phy_power_on(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
> +	struct device *dev = phy_common->dev;
> +	int err;
> +
> +	err = ufs_qcom_phy_enable_vreg(generic_phy,
> &phy_common->vdda_phy);
> +	if (err) {
> +		dev_err(dev, "%s enable vdda_phy failed, err=%d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	phy_common->phy_spec_ops->power_control(phy_common, true);
> +
> +	/* vdda_pll also enables ref clock LDOs so enable it first */
> +	err = ufs_qcom_phy_enable_vreg(generic_phy,
> &phy_common->vdda_pll);
> +	if (err) {
> +		dev_err(dev, "%s enable vdda_pll failed, err=%d\n",
> +			__func__, err);
> +		goto out_disable_phy;
> +	}
> +
> +	err = ufs_qcom_phy_enable_ref_clk(generic_phy);
> +	if (err) {
> +		dev_err(dev, "%s enable phy ref clock failed, err=%d\n",
> +			__func__, err);
> +		goto out_disable_pll;
> +	}
> +
> +	/* enable device PHY ref_clk pad rail */
> +	if (phy_common->vddp_ref_clk.reg) {
> +		err = ufs_qcom_phy_enable_vreg(generic_phy,
> +					       &phy_common->vddp_ref_clk);
> +		if (err) {
> +			dev_err(dev, "%s enable vddp_ref_clk failed,
> err=%d\n",
> +				__func__, err);
> +			goto out_disable_ref_clk;
> +		}
> +	}
> +
> +	phy_common->is_powered_on = true;
> +	goto out;
> +
> +out_disable_ref_clk:
> +	ufs_qcom_phy_disable_ref_clk(generic_phy);
> +out_disable_pll:
> +	ufs_qcom_phy_disable_vreg(generic_phy, &phy_common->vdda_pll);
> +out_disable_phy:
> +	ufs_qcom_phy_disable_vreg(generic_phy, &phy_common->vdda_phy);
> +out:
> +	return err;
> +}
> +
> +int ufs_qcom_phy_power_off(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
> +
> +	phy_common->phy_spec_ops->power_control(phy_common, false);
> +
> +	if (phy_common->vddp_ref_clk.reg)
> +		ufs_qcom_phy_disable_vreg(generic_phy,
> +					  &phy_common->vddp_ref_clk);
> +	ufs_qcom_phy_disable_ref_clk(generic_phy);
> +
> +	ufs_qcom_phy_disable_vreg(generic_phy, &phy_common->vdda_pll);
> +	ufs_qcom_phy_disable_vreg(generic_phy, &phy_common->vdda_phy);
> +	phy_common->is_powered_on = false;
> +
> +	return 0;
> +}
> --
> 1.8.5.2
>
> --
> QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
> of Code Aurora Forum, hosted by The Linux Foundation
> --
> To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>



^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v7 2/5] phy: qcom-ufs: add support for 20nm phy
  2015-01-15 14:32   ` Yaniv Gardi
@ 2015-01-15 15:18     ` dovl
  -1 siblings, 0 replies; 27+ messages in thread
From: dovl @ 2015-01-15 15:18 UTC (permalink / raw)
  Cc: james.bottomley, hch, linux-kernel, linux-scsi, linux-arm-msm,
	santoshsy, linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Kishon Vijay Abraham I, Grant Likely, Rob Herring,
	open list:OPEN FIRMWARE AND...

Reviewed-by: Dov Levenglick <dovl@codeaurora.org>

> This change adds a support for a 20nm qcom-ufs phy that is required in
> platforms that use ufs-qcom controller.
>
> Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>
>
> ---
>  drivers/phy/Makefile                |   1 +
>  drivers/phy/phy-qcom-ufs-i.h        |  43 +++++-
>  drivers/phy/phy-qcom-ufs-qmp-20nm.c | 257
> ++++++++++++++++++++++++++++++++++++
>  drivers/phy/phy-qcom-ufs-qmp-20nm.h | 235
> +++++++++++++++++++++++++++++++++
>  include/linux/phy/phy-qcom-ufs.h    |  59 +++++++++
>  5 files changed, 594 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.c
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.h
>  create mode 100644 include/linux/phy/phy-qcom-ufs.h
>
> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
> index 335965d..781b2fa 100644
> --- a/drivers/phy/Makefile
> +++ b/drivers/phy/Makefile
> @@ -35,3 +35,4 @@ obj-$(CONFIG_PHY_XGENE)			+=
> phy-xgene.o
>  obj-$(CONFIG_PHY_STIH407_USB)		+= phy-stih407-usb.o
>  obj-$(CONFIG_PHY_STIH41X_USB)		+= phy-stih41x-usb.o
>  obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs.o
> +obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs-qmp-20nm.o
> diff --git a/drivers/phy/phy-qcom-ufs-i.h b/drivers/phy/phy-qcom-ufs-i.h
> index dac200f..591a391 100644
> --- a/drivers/phy/phy-qcom-ufs-i.h
> +++ b/drivers/phy/phy-qcom-ufs-i.h
> @@ -15,15 +15,56 @@
>  #ifndef UFS_QCOM_PHY_I_H_
>  #define UFS_QCOM_PHY_I_H_
>
> +#include <linux/module.h>
>  #include <linux/clk.h>
> +#include <linux/regulator/consumer.h>
>  #include <linux/slab.h>
> -#include <linux/phy/phy.h>
> +#include <linux/phy/phy-qcom-ufs.h>
>  #include <linux/platform_device.h>
>  #include <linux/io.h>
>  #include <linux/delay.h>
>
> +#define readl_poll_timeout(addr, val, cond, sleep_us, timeout_us) \
> +({ \
> +	ktime_t timeout = ktime_add_us(ktime_get(), timeout_us); \
> +	might_sleep_if(timeout_us); \
> +	for (;;) { \
> +		(val) = readl(addr); \
> +		if (cond) \
> +			break; \
> +		if (timeout_us && ktime_compare(ktime_get(), timeout) > 0)
> { \
> +			(val) = readl(addr); \
> +			break; \
> +		} \
> +		if (sleep_us) \
> +			usleep_range(DIV_ROUND_UP(sleep_us, 4), sleep_us);
> \
> +	} \
> +	(cond) ? 0 : -ETIMEDOUT; \
> +})
> +
> +#define UFS_QCOM_PHY_CAL_ENTRY(reg, val)	\
> +	{				\
> +		.reg_offset = reg,	\
> +		.cfg_value = val,	\
> +	}
> +
>  #define UFS_QCOM_PHY_NAME_LEN	30
>
> +enum {
> +	MASK_SERDES_START       = 0x1,
> +	MASK_PCS_READY          = 0x1,
> +};
> +
> +enum {
> +	OFFSET_SERDES_START     = 0x0,
> +};
> +
> +struct ufs_qcom_phy_stored_attributes {
> +	u32 att;
> +	u32 value;
> +};
> +
> +
>  struct ufs_qcom_phy_calibration {
>  	u32 reg_offset;
>  	u32 cfg_value;
> diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.c
> b/drivers/phy/phy-qcom-ufs-qmp-20nm.c
> new file mode 100644
> index 0000000..8332f96
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs-qmp-20nm.c
> @@ -0,0 +1,257 @@
> +/*
> + * Copyright (c) 2013-2015, 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 "phy-qcom-ufs-qmp-20nm.h"
> +
> +#define UFS_PHY_NAME "ufs_phy_qmp_20nm"
> +
> +static
> +int ufs_qcom_phy_qmp_20nm_phy_calibrate(struct ufs_qcom_phy
> *ufs_qcom_phy,
> +					bool is_rate_B)
> +{
> +	struct ufs_qcom_phy_calibration *tbl_A, *tbl_B;
> +	int tbl_size_A, tbl_size_B;
> +	u8 major = ufs_qcom_phy->host_ctrl_rev_major;
> +	u16 minor = ufs_qcom_phy->host_ctrl_rev_minor;
> +	u16 step = ufs_qcom_phy->host_ctrl_rev_step;
> +	int err;
> +
> +	if ((major == 0x1) && (minor == 0x002) && (step == 0x0000)) {
> +		tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_2_0);
> +		tbl_A = phy_cal_table_rate_A_1_2_0;
> +	} else if ((major == 0x1) && (minor == 0x003) && (step == 0x0000))
> {
> +		tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_3_0);
> +		tbl_A = phy_cal_table_rate_A_1_3_0;
> +	} else {
> +		dev_err(ufs_qcom_phy->dev, "%s: Unknown UFS-PHY version,
> no calibration values\n",
> +			__func__);
> +		err = -ENODEV;
> +		goto out;
> +	}
> +
> +	tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
> +	tbl_B = phy_cal_table_rate_B;
> +
> +	err = ufs_qcom_phy_calibrate(ufs_qcom_phy, tbl_A, tbl_size_A,
> +						tbl_B, tbl_size_B,
> is_rate_B);
> +
> +	if (err)
> +		dev_err(ufs_qcom_phy->dev, "%s: ufs_qcom_phy_calibrate()
> failed %d\n",
> +			__func__, err);
> +
> +out:
> +	return err;
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_20nm_advertise_quirks(struct ufs_qcom_phy
> *phy_common)
> +{
> +	phy_common->quirks =
> +		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
> +}
> +
> +static int ufs_qcom_phy_qmp_20nm_init(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy_qmp_20nm *phy = phy_get_drvdata(generic_phy);
> +	struct ufs_qcom_phy *phy_common = &phy->common_cfg;
> +	int err = 0;
> +
> +	err = ufs_qcom_phy_init_clks(generic_phy, phy_common);
> +	if (err) {
> +		dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_clks()
> failed %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	err = ufs_qcom_phy_init_vregulators(generic_phy, phy_common);
> +	if (err) {
> +		dev_err(phy_common->dev, "%s:
> ufs_qcom_phy_init_vregulators() failed %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	ufs_qcom_phy_qmp_20nm_advertise_quirks(phy_common);
> +
> +out:
> +	return err;
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_20nm_power_control(struct ufs_qcom_phy *phy, bool
> val)
> +{
> +	bool hibern8_exit_after_pwr_collapse = phy->quirks &
> +		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
> +
> +	if (val) {
> +		writel_relaxed(0x1, phy->mmio +
> UFS_PHY_POWER_DOWN_CONTROL);
> +		/*
> +		 * Before any transactions involving PHY, ensure PHY knows
> +		 * that it's analog rail is powered ON.
> +		 */
> +		mb();
> +
> +		if (hibern8_exit_after_pwr_collapse) {
> +			/*
> +			 * Give atleast 1us delay after restoring PHY
> analog
> +			 * power.
> +			 */
> +			usleep_range(1, 2);
> +			writel_relaxed(0x0A, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			writel_relaxed(0x08, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			/*
> +			 * Make sure workaround is deactivated before
> proceeding
> +			 * with normal PHY operations.
> +			 */
> +			mb();
> +		}
> +	} else {
> +		if (hibern8_exit_after_pwr_collapse) {
> +			writel_relaxed(0x0A, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			writel_relaxed(0x02, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			/*
> +			 * Make sure that above workaround is activated
> before
> +			 * PHY analog power collapse.
> +			 */
> +			mb();
> +		}
> +
> +		writel_relaxed(0x0, phy->mmio +
> UFS_PHY_POWER_DOWN_CONTROL);
> +		/*
> +		 * ensure that PHY knows its PHY analog rail is going
> +		 * to be powered down
> +		 */
> +		mb();
> +	}
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_20nm_set_tx_lane_enable(struct ufs_qcom_phy *phy,
> u32 val)
> +{
> +	writel_relaxed(val & UFS_PHY_TX_LANE_ENABLE_MASK,
> +			phy->mmio + UFS_PHY_TX_LANE_ENABLE);
> +	mb();
> +}
> +
> +static inline void ufs_qcom_phy_qmp_20nm_start_serdes(struct ufs_qcom_phy
> *phy)
> +{
> +	u32 tmp;
> +
> +	tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
> +	tmp &= ~MASK_SERDES_START;
> +	tmp |= (1 << OFFSET_SERDES_START);
> +	writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
> +	mb();
> +}
> +
> +static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy
> *phy_common)
> +{
> +	int err = 0;
> +	u32 val;
> +
> +	err = readl_poll_timeout(phy_common->mmio +
> UFS_PHY_PCS_READY_STATUS,
> +			val, (val & MASK_PCS_READY), 10, 1000000);
> +	if (err)
> +		dev_err(phy_common->dev, "%s: poll for pcs failed err =
> %d\n",
> +			__func__, err);
> +	return err;
> +}
> +
> +static struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
> +	.init		= ufs_qcom_phy_qmp_20nm_init,
> +	.exit		= ufs_qcom_phy_exit,
> +	.power_on	= ufs_qcom_phy_power_on,
> +	.power_off	= ufs_qcom_phy_power_off,
> +	.owner		= THIS_MODULE,
> +};
> +
> +static struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
> +	.calibrate_phy		= ufs_qcom_phy_qmp_20nm_phy_calibrate,
> +	.start_serdes		= ufs_qcom_phy_qmp_20nm_start_serdes,
> +	.is_physical_coding_sublayer_ready =
> ufs_qcom_phy_qmp_20nm_is_pcs_ready,
> +	.set_tx_lane_enable	=
> ufs_qcom_phy_qmp_20nm_set_tx_lane_enable,
> +	.power_control		= ufs_qcom_phy_qmp_20nm_power_control,
> +};
> +
> +static int ufs_qcom_phy_qmp_20nm_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct phy *generic_phy;
> +	struct ufs_qcom_phy_qmp_20nm *phy;
> +	int err = 0;
> +
> +	phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
> +	if (!phy) {
> +		dev_err(dev, "%s: failed to allocate phy\n", __func__);
> +		err = -ENOMEM;
> +		goto out;
> +	}
> +
> +	generic_phy = ufs_qcom_phy_generic_probe(pdev, &phy->common_cfg,
> +				&ufs_qcom_phy_qmp_20nm_phy_ops,
> &phy_20nm_ops);
> +
> +	if (!generic_phy) {
> +		dev_err(dev, "%s: ufs_qcom_phy_generic_probe() failed\n",
> +			__func__);
> +		err = -EIO;
> +		goto out;
> +	}
> +
> +	phy_set_drvdata(generic_phy, phy);
> +
> +	strlcpy(phy->common_cfg.name, UFS_PHY_NAME,
> +			sizeof(phy->common_cfg.name));
> +
> +out:
> +	return err;
> +}
> +
> +static int ufs_qcom_phy_qmp_20nm_remove(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct phy *generic_phy = to_phy(dev);
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +	int err = 0;
> +
> +	err = ufs_qcom_phy_remove(generic_phy, ufs_qcom_phy);
> +	if (err)
> +		dev_err(dev, "%s: ufs_qcom_phy_remove failed = %d\n",
> +			__func__, err);
> +
> +	return err;
> +}
> +
> +static const struct of_device_id ufs_qcom_phy_qmp_20nm_of_match[] = {
> +	{.compatible = "qcom,ufs-phy-qmp-20nm"},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_20nm_of_match);
> +
> +static struct platform_driver ufs_qcom_phy_qmp_20nm_driver = {
> +	.probe = ufs_qcom_phy_qmp_20nm_probe,
> +	.remove = ufs_qcom_phy_qmp_20nm_remove,
> +	.driver = {
> +		.of_match_table = ufs_qcom_phy_qmp_20nm_of_match,
> +		.name = "ufs_qcom_phy_qmp_20nm",
> +		.owner = THIS_MODULE,
> +	},
> +};
> +
> +module_platform_driver(ufs_qcom_phy_qmp_20nm_driver);
> +
> +MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 20nm");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.h
> b/drivers/phy/phy-qcom-ufs-qmp-20nm.h
> new file mode 100644
> index 0000000..4f3076b
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs-qmp-20nm.h
> @@ -0,0 +1,235 @@
> +/*
> + * Copyright (c) 2013-2015, 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.
> + *
> + */
> +
> +#ifndef UFS_QCOM_PHY_QMP_20NM_H_
> +#define UFS_QCOM_PHY_QMP_20NM_H_
> +
> +#include "phy-qcom-ufs-i.h"
> +
> +/* QCOM UFS PHY control registers */
> +
> +#define COM_OFF(x)     (0x000 + x)
> +#define PHY_OFF(x)     (0xC00 + x)
> +#define TX_OFF(n, x)   (0x400 + (0x400 * n) + x)
> +#define RX_OFF(n, x)   (0x600 + (0x400 * n) + x)
> +
> +/* UFS PHY PLL block registers */
> +#define QSERDES_COM_SYS_CLK_CTRL		COM_OFF(0x0)
> +#define QSERDES_COM_PLL_VCOTAIL_EN		COM_OFF(0x04)
> +#define QSERDES_COM_PLL_CNTRL			COM_OFF(0x14)
> +#define QSERDES_COM_PLL_IP_SETI			COM_OFF(0x24)
> +#define QSERDES_COM_CORE_CLK_IN_SYNC_SEL	COM_OFF(0x28)
> +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN		COM_OFF(0x30)
> +#define QSERDES_COM_PLL_CP_SETI			COM_OFF(0x34)
> +#define QSERDES_COM_PLL_IP_SETP			COM_OFF(0x38)
> +#define QSERDES_COM_PLL_CP_SETP			COM_OFF(0x3C)
> +#define QSERDES_COM_SYSCLK_EN_SEL_TXBAND	COM_OFF(0x48)
> +#define QSERDES_COM_RESETSM_CNTRL		COM_OFF(0x4C)
> +#define QSERDES_COM_RESETSM_CNTRL2		COM_OFF(0x50)
> +#define QSERDES_COM_PLLLOCK_CMP1		COM_OFF(0x90)
> +#define QSERDES_COM_PLLLOCK_CMP2		COM_OFF(0x94)
> +#define QSERDES_COM_PLLLOCK_CMP3		COM_OFF(0x98)
> +#define QSERDES_COM_PLLLOCK_CMP_EN		COM_OFF(0x9C)
> +#define QSERDES_COM_BGTC			COM_OFF(0xA0)
> +#define QSERDES_COM_DEC_START1			COM_OFF(0xAC)
> +#define QSERDES_COM_PLL_AMP_OS			COM_OFF(0xB0)
> +#define QSERDES_COM_RES_CODE_UP_OFFSET		COM_OFF(0xD8)
> +#define QSERDES_COM_RES_CODE_DN_OFFSET		COM_OFF(0xDC)
> +#define QSERDES_COM_DIV_FRAC_START1		COM_OFF(0x100)
> +#define QSERDES_COM_DIV_FRAC_START2		COM_OFF(0x104)
> +#define QSERDES_COM_DIV_FRAC_START3		COM_OFF(0x108)
> +#define QSERDES_COM_DEC_START2			COM_OFF(0x10C)
> +#define QSERDES_COM_PLL_RXTXEPCLK_EN		COM_OFF(0x110)
> +#define QSERDES_COM_PLL_CRCTRL			COM_OFF(0x114)
> +#define QSERDES_COM_PLL_CLKEPDIV		COM_OFF(0x118)
> +
> +/* TX LANE n (0, 1) registers */
> +#define QSERDES_TX_EMP_POST1_LVL(n)		TX_OFF(n, 0x08)
> +#define QSERDES_TX_DRV_LVL(n)			TX_OFF(n, 0x0C)
> +#define QSERDES_TX_LANE_MODE(n)			TX_OFF(n, 0x54)
> +
> +/* RX LANE n (0, 1) registers */
> +#define QSERDES_RX_CDR_CONTROL1(n)		RX_OFF(n, 0x0)
> +#define QSERDES_RX_CDR_CONTROL_HALF(n)		RX_OFF(n, 0x8)
> +#define QSERDES_RX_RX_EQ_GAIN1_LSB(n)		RX_OFF(n, 0xA8)
> +#define QSERDES_RX_RX_EQ_GAIN1_MSB(n)		RX_OFF(n, 0xAC)
> +#define QSERDES_RX_RX_EQ_GAIN2_LSB(n)		RX_OFF(n, 0xB0)
> +#define QSERDES_RX_RX_EQ_GAIN2_MSB(n)		RX_OFF(n, 0xB4)
> +#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(n)	RX_OFF(n, 0xBC)
> +#define QSERDES_RX_CDR_CONTROL_QUARTER(n)	RX_OFF(n, 0xC)
> +#define QSERDES_RX_SIGDET_CNTRL(n)		RX_OFF(n, 0x100)
> +
> +/* UFS PHY registers */
> +#define UFS_PHY_PHY_START			PHY_OFF(0x00)
> +#define UFS_PHY_POWER_DOWN_CONTROL		PHY_OFF(0x4)
> +#define UFS_PHY_TX_LANE_ENABLE			PHY_OFF(0x44)
> +#define UFS_PHY_PWM_G1_CLK_DIVIDER		PHY_OFF(0x08)
> +#define UFS_PHY_PWM_G2_CLK_DIVIDER		PHY_OFF(0x0C)
> +#define UFS_PHY_PWM_G3_CLK_DIVIDER		PHY_OFF(0x10)
> +#define UFS_PHY_PWM_G4_CLK_DIVIDER		PHY_OFF(0x14)
> +#define UFS_PHY_CORECLK_PWM_G1_CLK_DIVIDER	PHY_OFF(0x34)
> +#define UFS_PHY_CORECLK_PWM_G2_CLK_DIVIDER	PHY_OFF(0x38)
> +#define UFS_PHY_CORECLK_PWM_G3_CLK_DIVIDER	PHY_OFF(0x3C)
> +#define UFS_PHY_CORECLK_PWM_G4_CLK_DIVIDER	PHY_OFF(0x40)
> +#define UFS_PHY_OMC_STATUS_RDVAL		PHY_OFF(0x68)
> +#define UFS_PHY_LINE_RESET_TIME			PHY_OFF(0x28)
> +#define UFS_PHY_LINE_RESET_GRANULARITY		PHY_OFF(0x2C)
> +#define UFS_PHY_TSYNC_RSYNC_CNTL		PHY_OFF(0x48)
> +#define UFS_PHY_PLL_CNTL			PHY_OFF(0x50)
> +#define UFS_PHY_TX_LARGE_AMP_DRV_LVL		PHY_OFF(0x54)
> +#define UFS_PHY_TX_SMALL_AMP_DRV_LVL		PHY_OFF(0x5C)
> +#define UFS_PHY_TX_LARGE_AMP_POST_EMP_LVL	PHY_OFF(0x58)
> +#define UFS_PHY_TX_SMALL_AMP_POST_EMP_LVL	PHY_OFF(0x60)
> +#define UFS_PHY_CFG_CHANGE_CNT_VAL		PHY_OFF(0x64)
> +#define UFS_PHY_RX_SYNC_WAIT_TIME		PHY_OFF(0x6C)
> +#define UFS_PHY_TX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xB4)
> +#define UFS_PHY_RX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xE0)
> +#define UFS_PHY_TX_MIN_STALL_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xB8)
> +#define UFS_PHY_RX_MIN_STALL_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xE4)
> +#define UFS_PHY_TX_MIN_SAVE_CONFIG_TIME_CAPABILITY	PHY_OFF(0xBC)
> +#define UFS_PHY_RX_MIN_SAVE_CONFIG_TIME_CAPABILITY	PHY_OFF(0xE8)
> +#define UFS_PHY_RX_PWM_BURST_CLOSURE_LENGTH_CAPABILITY	PHY_OFF(0xFC)
> +#define UFS_PHY_RX_MIN_ACTIVATETIME_CAPABILITY		PHY_OFF(0x100)
> +#define UFS_PHY_RX_SIGDET_CTRL3
> PHY_OFF(0x14c)
> +#define UFS_PHY_RMMI_ATTR_CTRL			PHY_OFF(0x160)
> +#define UFS_PHY_RMMI_RX_CFGUPDT_L1	(1 << 7)
> +#define UFS_PHY_RMMI_TX_CFGUPDT_L1	(1 << 6)
> +#define UFS_PHY_RMMI_CFGWR_L1		(1 << 5)
> +#define UFS_PHY_RMMI_CFGRD_L1		(1 << 4)
> +#define UFS_PHY_RMMI_RX_CFGUPDT_L0	(1 << 3)
> +#define UFS_PHY_RMMI_TX_CFGUPDT_L0	(1 << 2)
> +#define UFS_PHY_RMMI_CFGWR_L0		(1 << 1)
> +#define UFS_PHY_RMMI_CFGRD_L0		(1 << 0)
> +#define UFS_PHY_RMMI_ATTRID			PHY_OFF(0x164)
> +#define UFS_PHY_RMMI_ATTRWRVAL			PHY_OFF(0x168)
> +#define UFS_PHY_RMMI_ATTRRDVAL_L0_STATUS	PHY_OFF(0x16C)
> +#define UFS_PHY_RMMI_ATTRRDVAL_L1_STATUS	PHY_OFF(0x170)
> +#define UFS_PHY_PCS_READY_STATUS		PHY_OFF(0x174)
> +
> +#define UFS_PHY_TX_LANE_ENABLE_MASK		0x3
> +
> +/*
> + * This structure represents the 20nm specific phy.
> + * common_cfg MUST remain the first field in this structure
> + * in case extra fields are added. This way, when calling
> + * get_ufs_qcom_phy() of generic phy, we can extract the
> + * common phy structure (struct ufs_qcom_phy) out of it
> + * regardless of the relevant specific phy.
> + */
> +struct ufs_qcom_phy_qmp_20nm {
> +	struct ufs_qcom_phy common_cfg;
> +};
> +
> +static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_2_0[] = {
> +	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x3f),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x1b),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x0f),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(0), 0x2F),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(0), 0x20),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(1), 0x2F),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(1), 0x20),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3),
> +};
> +
> +static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_3_0[] = {
> +	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x2b),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x38),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x3c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_UP_OFFSET, 0x02),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_DN_OFFSET, 0x02),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CNTRL, 0x40),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3),
> +};
> +
> +static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x98),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0x65),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x1e),
> +};
> +
> +#endif
> diff --git a/include/linux/phy/phy-qcom-ufs.h
> b/include/linux/phy/phy-qcom-ufs.h
> new file mode 100644
> index 0000000..9d18e9f
> --- /dev/null
> +++ b/include/linux/phy/phy-qcom-ufs.h
> @@ -0,0 +1,59 @@
> +/*
> + * Copyright (c) 2013-2015, 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.
> + *
> + */
> +
> +#ifndef PHY_QCOM_UFS_H_
> +#define PHY_QCOM_UFS_H_
> +
> +#include "phy.h"
> +
> +/**
> + * ufs_qcom_phy_enable_ref_clk() - Enable the phy
> + * ref clock.
> + * @phy: reference to a generic phy
> + *
> + * returns 0 for success, and non-zero for error.
> + */
> +int ufs_qcom_phy_enable_ref_clk(struct phy *phy);
> +
> +/**
> + * ufs_qcom_phy_disable_ref_clk() - Disable the phy
> + * ref clock.
> + * @phy: reference to a generic phy.
> + */
> +void ufs_qcom_phy_disable_ref_clk(struct phy *phy);
> +
> +/**
> + * ufs_qcom_phy_enable_dev_ref_clk() - Enable the device
> + * ref clock.
> + * @phy: reference to a generic phy.
> + */
> +void ufs_qcom_phy_enable_dev_ref_clk(struct phy *phy);
> +
> +/**
> + * ufs_qcom_phy_disable_dev_ref_clk() - Disable the device
> + * ref clock.
> + * @phy: reference to a generic phy.
> + */
> +void ufs_qcom_phy_disable_dev_ref_clk(struct phy *phy);
> +
> +int ufs_qcom_phy_enable_iface_clk(struct phy *phy);
> +void ufs_qcom_phy_disable_iface_clk(struct phy *phy);
> +int ufs_qcom_phy_start_serdes(struct phy *phy);
> +int ufs_qcom_phy_set_tx_lane_enable(struct phy *phy, u32 tx_lanes);
> +int ufs_qcom_phy_calibrate_phy(struct phy *phy, bool is_rate_B);
> +int ufs_qcom_phy_is_pcs_ready(struct phy *phy);
> +void ufs_qcom_phy_save_controller_version(struct phy *phy,
> +			u8 major, u16 minor, u16 step);
> +
> +#endif /* PHY_QCOM_UFS_H_ */
> --
> 1.8.5.2
>
> --
> QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
> of Code Aurora Forum, hosted by The Linux Foundation
> --
> To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v7 2/5] phy: qcom-ufs: add support for 20nm phy
@ 2015-01-15 15:18     ` dovl
  0 siblings, 0 replies; 27+ messages in thread
From: dovl @ 2015-01-15 15:18 UTC (permalink / raw)
  To: Yaniv Gardi
  Cc: james.bottomley, hch, linux-kernel, linux-scsi, linux-arm-msm,
	santoshsy, linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Kishon Vijay Abraham I, Grant Likely, Rob Herring,
	open list:OPEN FIRMWARE AND...

Reviewed-by: Dov Levenglick <dovl@codeaurora.org>

> This change adds a support for a 20nm qcom-ufs phy that is required in
> platforms that use ufs-qcom controller.
>
> Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>
>
> ---
>  drivers/phy/Makefile                |   1 +
>  drivers/phy/phy-qcom-ufs-i.h        |  43 +++++-
>  drivers/phy/phy-qcom-ufs-qmp-20nm.c | 257
> ++++++++++++++++++++++++++++++++++++
>  drivers/phy/phy-qcom-ufs-qmp-20nm.h | 235
> +++++++++++++++++++++++++++++++++
>  include/linux/phy/phy-qcom-ufs.h    |  59 +++++++++
>  5 files changed, 594 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.c
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.h
>  create mode 100644 include/linux/phy/phy-qcom-ufs.h
>
> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
> index 335965d..781b2fa 100644
> --- a/drivers/phy/Makefile
> +++ b/drivers/phy/Makefile
> @@ -35,3 +35,4 @@ obj-$(CONFIG_PHY_XGENE)			+=
> phy-xgene.o
>  obj-$(CONFIG_PHY_STIH407_USB)		+= phy-stih407-usb.o
>  obj-$(CONFIG_PHY_STIH41X_USB)		+= phy-stih41x-usb.o
>  obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs.o
> +obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs-qmp-20nm.o
> diff --git a/drivers/phy/phy-qcom-ufs-i.h b/drivers/phy/phy-qcom-ufs-i.h
> index dac200f..591a391 100644
> --- a/drivers/phy/phy-qcom-ufs-i.h
> +++ b/drivers/phy/phy-qcom-ufs-i.h
> @@ -15,15 +15,56 @@
>  #ifndef UFS_QCOM_PHY_I_H_
>  #define UFS_QCOM_PHY_I_H_
>
> +#include <linux/module.h>
>  #include <linux/clk.h>
> +#include <linux/regulator/consumer.h>
>  #include <linux/slab.h>
> -#include <linux/phy/phy.h>
> +#include <linux/phy/phy-qcom-ufs.h>
>  #include <linux/platform_device.h>
>  #include <linux/io.h>
>  #include <linux/delay.h>
>
> +#define readl_poll_timeout(addr, val, cond, sleep_us, timeout_us) \
> +({ \
> +	ktime_t timeout = ktime_add_us(ktime_get(), timeout_us); \
> +	might_sleep_if(timeout_us); \
> +	for (;;) { \
> +		(val) = readl(addr); \
> +		if (cond) \
> +			break; \
> +		if (timeout_us && ktime_compare(ktime_get(), timeout) > 0)
> { \
> +			(val) = readl(addr); \
> +			break; \
> +		} \
> +		if (sleep_us) \
> +			usleep_range(DIV_ROUND_UP(sleep_us, 4), sleep_us);
> \
> +	} \
> +	(cond) ? 0 : -ETIMEDOUT; \
> +})
> +
> +#define UFS_QCOM_PHY_CAL_ENTRY(reg, val)	\
> +	{				\
> +		.reg_offset = reg,	\
> +		.cfg_value = val,	\
> +	}
> +
>  #define UFS_QCOM_PHY_NAME_LEN	30
>
> +enum {
> +	MASK_SERDES_START       = 0x1,
> +	MASK_PCS_READY          = 0x1,
> +};
> +
> +enum {
> +	OFFSET_SERDES_START     = 0x0,
> +};
> +
> +struct ufs_qcom_phy_stored_attributes {
> +	u32 att;
> +	u32 value;
> +};
> +
> +
>  struct ufs_qcom_phy_calibration {
>  	u32 reg_offset;
>  	u32 cfg_value;
> diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.c
> b/drivers/phy/phy-qcom-ufs-qmp-20nm.c
> new file mode 100644
> index 0000000..8332f96
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs-qmp-20nm.c
> @@ -0,0 +1,257 @@
> +/*
> + * Copyright (c) 2013-2015, 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 "phy-qcom-ufs-qmp-20nm.h"
> +
> +#define UFS_PHY_NAME "ufs_phy_qmp_20nm"
> +
> +static
> +int ufs_qcom_phy_qmp_20nm_phy_calibrate(struct ufs_qcom_phy
> *ufs_qcom_phy,
> +					bool is_rate_B)
> +{
> +	struct ufs_qcom_phy_calibration *tbl_A, *tbl_B;
> +	int tbl_size_A, tbl_size_B;
> +	u8 major = ufs_qcom_phy->host_ctrl_rev_major;
> +	u16 minor = ufs_qcom_phy->host_ctrl_rev_minor;
> +	u16 step = ufs_qcom_phy->host_ctrl_rev_step;
> +	int err;
> +
> +	if ((major == 0x1) && (minor == 0x002) && (step == 0x0000)) {
> +		tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_2_0);
> +		tbl_A = phy_cal_table_rate_A_1_2_0;
> +	} else if ((major == 0x1) && (minor == 0x003) && (step == 0x0000))
> {
> +		tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_3_0);
> +		tbl_A = phy_cal_table_rate_A_1_3_0;
> +	} else {
> +		dev_err(ufs_qcom_phy->dev, "%s: Unknown UFS-PHY version,
> no calibration values\n",
> +			__func__);
> +		err = -ENODEV;
> +		goto out;
> +	}
> +
> +	tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
> +	tbl_B = phy_cal_table_rate_B;
> +
> +	err = ufs_qcom_phy_calibrate(ufs_qcom_phy, tbl_A, tbl_size_A,
> +						tbl_B, tbl_size_B,
> is_rate_B);
> +
> +	if (err)
> +		dev_err(ufs_qcom_phy->dev, "%s: ufs_qcom_phy_calibrate()
> failed %d\n",
> +			__func__, err);
> +
> +out:
> +	return err;
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_20nm_advertise_quirks(struct ufs_qcom_phy
> *phy_common)
> +{
> +	phy_common->quirks =
> +		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
> +}
> +
> +static int ufs_qcom_phy_qmp_20nm_init(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy_qmp_20nm *phy = phy_get_drvdata(generic_phy);
> +	struct ufs_qcom_phy *phy_common = &phy->common_cfg;
> +	int err = 0;
> +
> +	err = ufs_qcom_phy_init_clks(generic_phy, phy_common);
> +	if (err) {
> +		dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_clks()
> failed %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	err = ufs_qcom_phy_init_vregulators(generic_phy, phy_common);
> +	if (err) {
> +		dev_err(phy_common->dev, "%s:
> ufs_qcom_phy_init_vregulators() failed %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	ufs_qcom_phy_qmp_20nm_advertise_quirks(phy_common);
> +
> +out:
> +	return err;
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_20nm_power_control(struct ufs_qcom_phy *phy, bool
> val)
> +{
> +	bool hibern8_exit_after_pwr_collapse = phy->quirks &
> +		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
> +
> +	if (val) {
> +		writel_relaxed(0x1, phy->mmio +
> UFS_PHY_POWER_DOWN_CONTROL);
> +		/*
> +		 * Before any transactions involving PHY, ensure PHY knows
> +		 * that it's analog rail is powered ON.
> +		 */
> +		mb();
> +
> +		if (hibern8_exit_after_pwr_collapse) {
> +			/*
> +			 * Give atleast 1us delay after restoring PHY
> analog
> +			 * power.
> +			 */
> +			usleep_range(1, 2);
> +			writel_relaxed(0x0A, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			writel_relaxed(0x08, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			/*
> +			 * Make sure workaround is deactivated before
> proceeding
> +			 * with normal PHY operations.
> +			 */
> +			mb();
> +		}
> +	} else {
> +		if (hibern8_exit_after_pwr_collapse) {
> +			writel_relaxed(0x0A, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			writel_relaxed(0x02, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			/*
> +			 * Make sure that above workaround is activated
> before
> +			 * PHY analog power collapse.
> +			 */
> +			mb();
> +		}
> +
> +		writel_relaxed(0x0, phy->mmio +
> UFS_PHY_POWER_DOWN_CONTROL);
> +		/*
> +		 * ensure that PHY knows its PHY analog rail is going
> +		 * to be powered down
> +		 */
> +		mb();
> +	}
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_20nm_set_tx_lane_enable(struct ufs_qcom_phy *phy,
> u32 val)
> +{
> +	writel_relaxed(val & UFS_PHY_TX_LANE_ENABLE_MASK,
> +			phy->mmio + UFS_PHY_TX_LANE_ENABLE);
> +	mb();
> +}
> +
> +static inline void ufs_qcom_phy_qmp_20nm_start_serdes(struct ufs_qcom_phy
> *phy)
> +{
> +	u32 tmp;
> +
> +	tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
> +	tmp &= ~MASK_SERDES_START;
> +	tmp |= (1 << OFFSET_SERDES_START);
> +	writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
> +	mb();
> +}
> +
> +static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy
> *phy_common)
> +{
> +	int err = 0;
> +	u32 val;
> +
> +	err = readl_poll_timeout(phy_common->mmio +
> UFS_PHY_PCS_READY_STATUS,
> +			val, (val & MASK_PCS_READY), 10, 1000000);
> +	if (err)
> +		dev_err(phy_common->dev, "%s: poll for pcs failed err =
> %d\n",
> +			__func__, err);
> +	return err;
> +}
> +
> +static struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
> +	.init		= ufs_qcom_phy_qmp_20nm_init,
> +	.exit		= ufs_qcom_phy_exit,
> +	.power_on	= ufs_qcom_phy_power_on,
> +	.power_off	= ufs_qcom_phy_power_off,
> +	.owner		= THIS_MODULE,
> +};
> +
> +static struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
> +	.calibrate_phy		= ufs_qcom_phy_qmp_20nm_phy_calibrate,
> +	.start_serdes		= ufs_qcom_phy_qmp_20nm_start_serdes,
> +	.is_physical_coding_sublayer_ready =
> ufs_qcom_phy_qmp_20nm_is_pcs_ready,
> +	.set_tx_lane_enable	=
> ufs_qcom_phy_qmp_20nm_set_tx_lane_enable,
> +	.power_control		= ufs_qcom_phy_qmp_20nm_power_control,
> +};
> +
> +static int ufs_qcom_phy_qmp_20nm_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct phy *generic_phy;
> +	struct ufs_qcom_phy_qmp_20nm *phy;
> +	int err = 0;
> +
> +	phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
> +	if (!phy) {
> +		dev_err(dev, "%s: failed to allocate phy\n", __func__);
> +		err = -ENOMEM;
> +		goto out;
> +	}
> +
> +	generic_phy = ufs_qcom_phy_generic_probe(pdev, &phy->common_cfg,
> +				&ufs_qcom_phy_qmp_20nm_phy_ops,
> &phy_20nm_ops);
> +
> +	if (!generic_phy) {
> +		dev_err(dev, "%s: ufs_qcom_phy_generic_probe() failed\n",
> +			__func__);
> +		err = -EIO;
> +		goto out;
> +	}
> +
> +	phy_set_drvdata(generic_phy, phy);
> +
> +	strlcpy(phy->common_cfg.name, UFS_PHY_NAME,
> +			sizeof(phy->common_cfg.name));
> +
> +out:
> +	return err;
> +}
> +
> +static int ufs_qcom_phy_qmp_20nm_remove(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct phy *generic_phy = to_phy(dev);
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +	int err = 0;
> +
> +	err = ufs_qcom_phy_remove(generic_phy, ufs_qcom_phy);
> +	if (err)
> +		dev_err(dev, "%s: ufs_qcom_phy_remove failed = %d\n",
> +			__func__, err);
> +
> +	return err;
> +}
> +
> +static const struct of_device_id ufs_qcom_phy_qmp_20nm_of_match[] = {
> +	{.compatible = "qcom,ufs-phy-qmp-20nm"},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_20nm_of_match);
> +
> +static struct platform_driver ufs_qcom_phy_qmp_20nm_driver = {
> +	.probe = ufs_qcom_phy_qmp_20nm_probe,
> +	.remove = ufs_qcom_phy_qmp_20nm_remove,
> +	.driver = {
> +		.of_match_table = ufs_qcom_phy_qmp_20nm_of_match,
> +		.name = "ufs_qcom_phy_qmp_20nm",
> +		.owner = THIS_MODULE,
> +	},
> +};
> +
> +module_platform_driver(ufs_qcom_phy_qmp_20nm_driver);
> +
> +MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 20nm");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.h
> b/drivers/phy/phy-qcom-ufs-qmp-20nm.h
> new file mode 100644
> index 0000000..4f3076b
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs-qmp-20nm.h
> @@ -0,0 +1,235 @@
> +/*
> + * Copyright (c) 2013-2015, 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.
> + *
> + */
> +
> +#ifndef UFS_QCOM_PHY_QMP_20NM_H_
> +#define UFS_QCOM_PHY_QMP_20NM_H_
> +
> +#include "phy-qcom-ufs-i.h"
> +
> +/* QCOM UFS PHY control registers */
> +
> +#define COM_OFF(x)     (0x000 + x)
> +#define PHY_OFF(x)     (0xC00 + x)
> +#define TX_OFF(n, x)   (0x400 + (0x400 * n) + x)
> +#define RX_OFF(n, x)   (0x600 + (0x400 * n) + x)
> +
> +/* UFS PHY PLL block registers */
> +#define QSERDES_COM_SYS_CLK_CTRL		COM_OFF(0x0)
> +#define QSERDES_COM_PLL_VCOTAIL_EN		COM_OFF(0x04)
> +#define QSERDES_COM_PLL_CNTRL			COM_OFF(0x14)
> +#define QSERDES_COM_PLL_IP_SETI			COM_OFF(0x24)
> +#define QSERDES_COM_CORE_CLK_IN_SYNC_SEL	COM_OFF(0x28)
> +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN		COM_OFF(0x30)
> +#define QSERDES_COM_PLL_CP_SETI			COM_OFF(0x34)
> +#define QSERDES_COM_PLL_IP_SETP			COM_OFF(0x38)
> +#define QSERDES_COM_PLL_CP_SETP			COM_OFF(0x3C)
> +#define QSERDES_COM_SYSCLK_EN_SEL_TXBAND	COM_OFF(0x48)
> +#define QSERDES_COM_RESETSM_CNTRL		COM_OFF(0x4C)
> +#define QSERDES_COM_RESETSM_CNTRL2		COM_OFF(0x50)
> +#define QSERDES_COM_PLLLOCK_CMP1		COM_OFF(0x90)
> +#define QSERDES_COM_PLLLOCK_CMP2		COM_OFF(0x94)
> +#define QSERDES_COM_PLLLOCK_CMP3		COM_OFF(0x98)
> +#define QSERDES_COM_PLLLOCK_CMP_EN		COM_OFF(0x9C)
> +#define QSERDES_COM_BGTC			COM_OFF(0xA0)
> +#define QSERDES_COM_DEC_START1			COM_OFF(0xAC)
> +#define QSERDES_COM_PLL_AMP_OS			COM_OFF(0xB0)
> +#define QSERDES_COM_RES_CODE_UP_OFFSET		COM_OFF(0xD8)
> +#define QSERDES_COM_RES_CODE_DN_OFFSET		COM_OFF(0xDC)
> +#define QSERDES_COM_DIV_FRAC_START1		COM_OFF(0x100)
> +#define QSERDES_COM_DIV_FRAC_START2		COM_OFF(0x104)
> +#define QSERDES_COM_DIV_FRAC_START3		COM_OFF(0x108)
> +#define QSERDES_COM_DEC_START2			COM_OFF(0x10C)
> +#define QSERDES_COM_PLL_RXTXEPCLK_EN		COM_OFF(0x110)
> +#define QSERDES_COM_PLL_CRCTRL			COM_OFF(0x114)
> +#define QSERDES_COM_PLL_CLKEPDIV		COM_OFF(0x118)
> +
> +/* TX LANE n (0, 1) registers */
> +#define QSERDES_TX_EMP_POST1_LVL(n)		TX_OFF(n, 0x08)
> +#define QSERDES_TX_DRV_LVL(n)			TX_OFF(n, 0x0C)
> +#define QSERDES_TX_LANE_MODE(n)			TX_OFF(n, 0x54)
> +
> +/* RX LANE n (0, 1) registers */
> +#define QSERDES_RX_CDR_CONTROL1(n)		RX_OFF(n, 0x0)
> +#define QSERDES_RX_CDR_CONTROL_HALF(n)		RX_OFF(n, 0x8)
> +#define QSERDES_RX_RX_EQ_GAIN1_LSB(n)		RX_OFF(n, 0xA8)
> +#define QSERDES_RX_RX_EQ_GAIN1_MSB(n)		RX_OFF(n, 0xAC)
> +#define QSERDES_RX_RX_EQ_GAIN2_LSB(n)		RX_OFF(n, 0xB0)
> +#define QSERDES_RX_RX_EQ_GAIN2_MSB(n)		RX_OFF(n, 0xB4)
> +#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(n)	RX_OFF(n, 0xBC)
> +#define QSERDES_RX_CDR_CONTROL_QUARTER(n)	RX_OFF(n, 0xC)
> +#define QSERDES_RX_SIGDET_CNTRL(n)		RX_OFF(n, 0x100)
> +
> +/* UFS PHY registers */
> +#define UFS_PHY_PHY_START			PHY_OFF(0x00)
> +#define UFS_PHY_POWER_DOWN_CONTROL		PHY_OFF(0x4)
> +#define UFS_PHY_TX_LANE_ENABLE			PHY_OFF(0x44)
> +#define UFS_PHY_PWM_G1_CLK_DIVIDER		PHY_OFF(0x08)
> +#define UFS_PHY_PWM_G2_CLK_DIVIDER		PHY_OFF(0x0C)
> +#define UFS_PHY_PWM_G3_CLK_DIVIDER		PHY_OFF(0x10)
> +#define UFS_PHY_PWM_G4_CLK_DIVIDER		PHY_OFF(0x14)
> +#define UFS_PHY_CORECLK_PWM_G1_CLK_DIVIDER	PHY_OFF(0x34)
> +#define UFS_PHY_CORECLK_PWM_G2_CLK_DIVIDER	PHY_OFF(0x38)
> +#define UFS_PHY_CORECLK_PWM_G3_CLK_DIVIDER	PHY_OFF(0x3C)
> +#define UFS_PHY_CORECLK_PWM_G4_CLK_DIVIDER	PHY_OFF(0x40)
> +#define UFS_PHY_OMC_STATUS_RDVAL		PHY_OFF(0x68)
> +#define UFS_PHY_LINE_RESET_TIME			PHY_OFF(0x28)
> +#define UFS_PHY_LINE_RESET_GRANULARITY		PHY_OFF(0x2C)
> +#define UFS_PHY_TSYNC_RSYNC_CNTL		PHY_OFF(0x48)
> +#define UFS_PHY_PLL_CNTL			PHY_OFF(0x50)
> +#define UFS_PHY_TX_LARGE_AMP_DRV_LVL		PHY_OFF(0x54)
> +#define UFS_PHY_TX_SMALL_AMP_DRV_LVL		PHY_OFF(0x5C)
> +#define UFS_PHY_TX_LARGE_AMP_POST_EMP_LVL	PHY_OFF(0x58)
> +#define UFS_PHY_TX_SMALL_AMP_POST_EMP_LVL	PHY_OFF(0x60)
> +#define UFS_PHY_CFG_CHANGE_CNT_VAL		PHY_OFF(0x64)
> +#define UFS_PHY_RX_SYNC_WAIT_TIME		PHY_OFF(0x6C)
> +#define UFS_PHY_TX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xB4)
> +#define UFS_PHY_RX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xE0)
> +#define UFS_PHY_TX_MIN_STALL_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xB8)
> +#define UFS_PHY_RX_MIN_STALL_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xE4)
> +#define UFS_PHY_TX_MIN_SAVE_CONFIG_TIME_CAPABILITY	PHY_OFF(0xBC)
> +#define UFS_PHY_RX_MIN_SAVE_CONFIG_TIME_CAPABILITY	PHY_OFF(0xE8)
> +#define UFS_PHY_RX_PWM_BURST_CLOSURE_LENGTH_CAPABILITY	PHY_OFF(0xFC)
> +#define UFS_PHY_RX_MIN_ACTIVATETIME_CAPABILITY		PHY_OFF(0x100)
> +#define UFS_PHY_RX_SIGDET_CTRL3
> PHY_OFF(0x14c)
> +#define UFS_PHY_RMMI_ATTR_CTRL			PHY_OFF(0x160)
> +#define UFS_PHY_RMMI_RX_CFGUPDT_L1	(1 << 7)
> +#define UFS_PHY_RMMI_TX_CFGUPDT_L1	(1 << 6)
> +#define UFS_PHY_RMMI_CFGWR_L1		(1 << 5)
> +#define UFS_PHY_RMMI_CFGRD_L1		(1 << 4)
> +#define UFS_PHY_RMMI_RX_CFGUPDT_L0	(1 << 3)
> +#define UFS_PHY_RMMI_TX_CFGUPDT_L0	(1 << 2)
> +#define UFS_PHY_RMMI_CFGWR_L0		(1 << 1)
> +#define UFS_PHY_RMMI_CFGRD_L0		(1 << 0)
> +#define UFS_PHY_RMMI_ATTRID			PHY_OFF(0x164)
> +#define UFS_PHY_RMMI_ATTRWRVAL			PHY_OFF(0x168)
> +#define UFS_PHY_RMMI_ATTRRDVAL_L0_STATUS	PHY_OFF(0x16C)
> +#define UFS_PHY_RMMI_ATTRRDVAL_L1_STATUS	PHY_OFF(0x170)
> +#define UFS_PHY_PCS_READY_STATUS		PHY_OFF(0x174)
> +
> +#define UFS_PHY_TX_LANE_ENABLE_MASK		0x3
> +
> +/*
> + * This structure represents the 20nm specific phy.
> + * common_cfg MUST remain the first field in this structure
> + * in case extra fields are added. This way, when calling
> + * get_ufs_qcom_phy() of generic phy, we can extract the
> + * common phy structure (struct ufs_qcom_phy) out of it
> + * regardless of the relevant specific phy.
> + */
> +struct ufs_qcom_phy_qmp_20nm {
> +	struct ufs_qcom_phy common_cfg;
> +};
> +
> +static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_2_0[] = {
> +	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x3f),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x1b),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x0f),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(0), 0x2F),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(0), 0x20),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(1), 0x2F),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(1), 0x20),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3),
> +};
> +
> +static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_3_0[] = {
> +	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x2b),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x38),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x3c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_UP_OFFSET, 0x02),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_DN_OFFSET, 0x02),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CNTRL, 0x40),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3),
> +};
> +
> +static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x98),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0x65),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x1e),
> +};
> +
> +#endif
> diff --git a/include/linux/phy/phy-qcom-ufs.h
> b/include/linux/phy/phy-qcom-ufs.h
> new file mode 100644
> index 0000000..9d18e9f
> --- /dev/null
> +++ b/include/linux/phy/phy-qcom-ufs.h
> @@ -0,0 +1,59 @@
> +/*
> + * Copyright (c) 2013-2015, 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.
> + *
> + */
> +
> +#ifndef PHY_QCOM_UFS_H_
> +#define PHY_QCOM_UFS_H_
> +
> +#include "phy.h"
> +
> +/**
> + * ufs_qcom_phy_enable_ref_clk() - Enable the phy
> + * ref clock.
> + * @phy: reference to a generic phy
> + *
> + * returns 0 for success, and non-zero for error.
> + */
> +int ufs_qcom_phy_enable_ref_clk(struct phy *phy);
> +
> +/**
> + * ufs_qcom_phy_disable_ref_clk() - Disable the phy
> + * ref clock.
> + * @phy: reference to a generic phy.
> + */
> +void ufs_qcom_phy_disable_ref_clk(struct phy *phy);
> +
> +/**
> + * ufs_qcom_phy_enable_dev_ref_clk() - Enable the device
> + * ref clock.
> + * @phy: reference to a generic phy.
> + */
> +void ufs_qcom_phy_enable_dev_ref_clk(struct phy *phy);
> +
> +/**
> + * ufs_qcom_phy_disable_dev_ref_clk() - Disable the device
> + * ref clock.
> + * @phy: reference to a generic phy.
> + */
> +void ufs_qcom_phy_disable_dev_ref_clk(struct phy *phy);
> +
> +int ufs_qcom_phy_enable_iface_clk(struct phy *phy);
> +void ufs_qcom_phy_disable_iface_clk(struct phy *phy);
> +int ufs_qcom_phy_start_serdes(struct phy *phy);
> +int ufs_qcom_phy_set_tx_lane_enable(struct phy *phy, u32 tx_lanes);
> +int ufs_qcom_phy_calibrate_phy(struct phy *phy, bool is_rate_B);
> +int ufs_qcom_phy_is_pcs_ready(struct phy *phy);
> +void ufs_qcom_phy_save_controller_version(struct phy *phy,
> +			u8 major, u16 minor, u16 step);
> +
> +#endif /* PHY_QCOM_UFS_H_ */
> --
> 1.8.5.2
>
> --
> QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
> of Code Aurora Forum, hosted by The Linux Foundation
> --
> To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>



^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v7 2/5] phy: qcom-ufs: add support for 20nm phy
  2015-01-15 14:32   ` Yaniv Gardi
@ 2015-01-15 15:18     ` dovl
  -1 siblings, 0 replies; 27+ messages in thread
From: dovl @ 2015-01-15 15:18 UTC (permalink / raw)
  Cc: james.bottomley, hch, linux-kernel, linux-scsi, linux-arm-msm,
	santoshsy, linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Kishon Vijay Abraham I, Grant Likely, Rob Herring,
	open list:OPEN FIRMWARE AND...

Reviewed-by: Dov Levenglick <dovl@codeaurora.org>

> This change adds a support for a 20nm qcom-ufs phy that is required in
> platforms that use ufs-qcom controller.
>
> Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>
>
> ---
>  drivers/phy/Makefile                |   1 +
>  drivers/phy/phy-qcom-ufs-i.h        |  43 +++++-
>  drivers/phy/phy-qcom-ufs-qmp-20nm.c | 257
> ++++++++++++++++++++++++++++++++++++
>  drivers/phy/phy-qcom-ufs-qmp-20nm.h | 235
> +++++++++++++++++++++++++++++++++
>  include/linux/phy/phy-qcom-ufs.h    |  59 +++++++++
>  5 files changed, 594 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.c
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.h
>  create mode 100644 include/linux/phy/phy-qcom-ufs.h
>
> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
> index 335965d..781b2fa 100644
> --- a/drivers/phy/Makefile
> +++ b/drivers/phy/Makefile
> @@ -35,3 +35,4 @@ obj-$(CONFIG_PHY_XGENE)			+=
> phy-xgene.o
>  obj-$(CONFIG_PHY_STIH407_USB)		+= phy-stih407-usb.o
>  obj-$(CONFIG_PHY_STIH41X_USB)		+= phy-stih41x-usb.o
>  obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs.o
> +obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs-qmp-20nm.o
> diff --git a/drivers/phy/phy-qcom-ufs-i.h b/drivers/phy/phy-qcom-ufs-i.h
> index dac200f..591a391 100644
> --- a/drivers/phy/phy-qcom-ufs-i.h
> +++ b/drivers/phy/phy-qcom-ufs-i.h
> @@ -15,15 +15,56 @@
>  #ifndef UFS_QCOM_PHY_I_H_
>  #define UFS_QCOM_PHY_I_H_
>
> +#include <linux/module.h>
>  #include <linux/clk.h>
> +#include <linux/regulator/consumer.h>
>  #include <linux/slab.h>
> -#include <linux/phy/phy.h>
> +#include <linux/phy/phy-qcom-ufs.h>
>  #include <linux/platform_device.h>
>  #include <linux/io.h>
>  #include <linux/delay.h>
>
> +#define readl_poll_timeout(addr, val, cond, sleep_us, timeout_us) \
> +({ \
> +	ktime_t timeout = ktime_add_us(ktime_get(), timeout_us); \
> +	might_sleep_if(timeout_us); \
> +	for (;;) { \
> +		(val) = readl(addr); \
> +		if (cond) \
> +			break; \
> +		if (timeout_us && ktime_compare(ktime_get(), timeout) > 0)
> { \
> +			(val) = readl(addr); \
> +			break; \
> +		} \
> +		if (sleep_us) \
> +			usleep_range(DIV_ROUND_UP(sleep_us, 4), sleep_us);
> \
> +	} \
> +	(cond) ? 0 : -ETIMEDOUT; \
> +})
> +
> +#define UFS_QCOM_PHY_CAL_ENTRY(reg, val)	\
> +	{				\
> +		.reg_offset = reg,	\
> +		.cfg_value = val,	\
> +	}
> +
>  #define UFS_QCOM_PHY_NAME_LEN	30
>
> +enum {
> +	MASK_SERDES_START       = 0x1,
> +	MASK_PCS_READY          = 0x1,
> +};
> +
> +enum {
> +	OFFSET_SERDES_START     = 0x0,
> +};
> +
> +struct ufs_qcom_phy_stored_attributes {
> +	u32 att;
> +	u32 value;
> +};
> +
> +
>  struct ufs_qcom_phy_calibration {
>  	u32 reg_offset;
>  	u32 cfg_value;
> diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.c
> b/drivers/phy/phy-qcom-ufs-qmp-20nm.c
> new file mode 100644
> index 0000000..8332f96
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs-qmp-20nm.c
> @@ -0,0 +1,257 @@
> +/*
> + * Copyright (c) 2013-2015, 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 "phy-qcom-ufs-qmp-20nm.h"
> +
> +#define UFS_PHY_NAME "ufs_phy_qmp_20nm"
> +
> +static
> +int ufs_qcom_phy_qmp_20nm_phy_calibrate(struct ufs_qcom_phy
> *ufs_qcom_phy,
> +					bool is_rate_B)
> +{
> +	struct ufs_qcom_phy_calibration *tbl_A, *tbl_B;
> +	int tbl_size_A, tbl_size_B;
> +	u8 major = ufs_qcom_phy->host_ctrl_rev_major;
> +	u16 minor = ufs_qcom_phy->host_ctrl_rev_minor;
> +	u16 step = ufs_qcom_phy->host_ctrl_rev_step;
> +	int err;
> +
> +	if ((major == 0x1) && (minor == 0x002) && (step == 0x0000)) {
> +		tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_2_0);
> +		tbl_A = phy_cal_table_rate_A_1_2_0;
> +	} else if ((major == 0x1) && (minor == 0x003) && (step == 0x0000))
> {
> +		tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_3_0);
> +		tbl_A = phy_cal_table_rate_A_1_3_0;
> +	} else {
> +		dev_err(ufs_qcom_phy->dev, "%s: Unknown UFS-PHY version,
> no calibration values\n",
> +			__func__);
> +		err = -ENODEV;
> +		goto out;
> +	}
> +
> +	tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
> +	tbl_B = phy_cal_table_rate_B;
> +
> +	err = ufs_qcom_phy_calibrate(ufs_qcom_phy, tbl_A, tbl_size_A,
> +						tbl_B, tbl_size_B,
> is_rate_B);
> +
> +	if (err)
> +		dev_err(ufs_qcom_phy->dev, "%s: ufs_qcom_phy_calibrate()
> failed %d\n",
> +			__func__, err);
> +
> +out:
> +	return err;
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_20nm_advertise_quirks(struct ufs_qcom_phy
> *phy_common)
> +{
> +	phy_common->quirks =
> +		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
> +}
> +
> +static int ufs_qcom_phy_qmp_20nm_init(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy_qmp_20nm *phy = phy_get_drvdata(generic_phy);
> +	struct ufs_qcom_phy *phy_common = &phy->common_cfg;
> +	int err = 0;
> +
> +	err = ufs_qcom_phy_init_clks(generic_phy, phy_common);
> +	if (err) {
> +		dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_clks()
> failed %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	err = ufs_qcom_phy_init_vregulators(generic_phy, phy_common);
> +	if (err) {
> +		dev_err(phy_common->dev, "%s:
> ufs_qcom_phy_init_vregulators() failed %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	ufs_qcom_phy_qmp_20nm_advertise_quirks(phy_common);
> +
> +out:
> +	return err;
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_20nm_power_control(struct ufs_qcom_phy *phy, bool
> val)
> +{
> +	bool hibern8_exit_after_pwr_collapse = phy->quirks &
> +		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
> +
> +	if (val) {
> +		writel_relaxed(0x1, phy->mmio +
> UFS_PHY_POWER_DOWN_CONTROL);
> +		/*
> +		 * Before any transactions involving PHY, ensure PHY knows
> +		 * that it's analog rail is powered ON.
> +		 */
> +		mb();
> +
> +		if (hibern8_exit_after_pwr_collapse) {
> +			/*
> +			 * Give atleast 1us delay after restoring PHY
> analog
> +			 * power.
> +			 */
> +			usleep_range(1, 2);
> +			writel_relaxed(0x0A, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			writel_relaxed(0x08, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			/*
> +			 * Make sure workaround is deactivated before
> proceeding
> +			 * with normal PHY operations.
> +			 */
> +			mb();
> +		}
> +	} else {
> +		if (hibern8_exit_after_pwr_collapse) {
> +			writel_relaxed(0x0A, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			writel_relaxed(0x02, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			/*
> +			 * Make sure that above workaround is activated
> before
> +			 * PHY analog power collapse.
> +			 */
> +			mb();
> +		}
> +
> +		writel_relaxed(0x0, phy->mmio +
> UFS_PHY_POWER_DOWN_CONTROL);
> +		/*
> +		 * ensure that PHY knows its PHY analog rail is going
> +		 * to be powered down
> +		 */
> +		mb();
> +	}
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_20nm_set_tx_lane_enable(struct ufs_qcom_phy *phy,
> u32 val)
> +{
> +	writel_relaxed(val & UFS_PHY_TX_LANE_ENABLE_MASK,
> +			phy->mmio + UFS_PHY_TX_LANE_ENABLE);
> +	mb();
> +}
> +
> +static inline void ufs_qcom_phy_qmp_20nm_start_serdes(struct ufs_qcom_phy
> *phy)
> +{
> +	u32 tmp;
> +
> +	tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
> +	tmp &= ~MASK_SERDES_START;
> +	tmp |= (1 << OFFSET_SERDES_START);
> +	writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
> +	mb();
> +}
> +
> +static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy
> *phy_common)
> +{
> +	int err = 0;
> +	u32 val;
> +
> +	err = readl_poll_timeout(phy_common->mmio +
> UFS_PHY_PCS_READY_STATUS,
> +			val, (val & MASK_PCS_READY), 10, 1000000);
> +	if (err)
> +		dev_err(phy_common->dev, "%s: poll for pcs failed err =
> %d\n",
> +			__func__, err);
> +	return err;
> +}
> +
> +static struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
> +	.init		= ufs_qcom_phy_qmp_20nm_init,
> +	.exit		= ufs_qcom_phy_exit,
> +	.power_on	= ufs_qcom_phy_power_on,
> +	.power_off	= ufs_qcom_phy_power_off,
> +	.owner		= THIS_MODULE,
> +};
> +
> +static struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
> +	.calibrate_phy		= ufs_qcom_phy_qmp_20nm_phy_calibrate,
> +	.start_serdes		= ufs_qcom_phy_qmp_20nm_start_serdes,
> +	.is_physical_coding_sublayer_ready =
> ufs_qcom_phy_qmp_20nm_is_pcs_ready,
> +	.set_tx_lane_enable	=
> ufs_qcom_phy_qmp_20nm_set_tx_lane_enable,
> +	.power_control		= ufs_qcom_phy_qmp_20nm_power_control,
> +};
> +
> +static int ufs_qcom_phy_qmp_20nm_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct phy *generic_phy;
> +	struct ufs_qcom_phy_qmp_20nm *phy;
> +	int err = 0;
> +
> +	phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
> +	if (!phy) {
> +		dev_err(dev, "%s: failed to allocate phy\n", __func__);
> +		err = -ENOMEM;
> +		goto out;
> +	}
> +
> +	generic_phy = ufs_qcom_phy_generic_probe(pdev, &phy->common_cfg,
> +				&ufs_qcom_phy_qmp_20nm_phy_ops,
> &phy_20nm_ops);
> +
> +	if (!generic_phy) {
> +		dev_err(dev, "%s: ufs_qcom_phy_generic_probe() failed\n",
> +			__func__);
> +		err = -EIO;
> +		goto out;
> +	}
> +
> +	phy_set_drvdata(generic_phy, phy);
> +
> +	strlcpy(phy->common_cfg.name, UFS_PHY_NAME,
> +			sizeof(phy->common_cfg.name));
> +
> +out:
> +	return err;
> +}
> +
> +static int ufs_qcom_phy_qmp_20nm_remove(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct phy *generic_phy = to_phy(dev);
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +	int err = 0;
> +
> +	err = ufs_qcom_phy_remove(generic_phy, ufs_qcom_phy);
> +	if (err)
> +		dev_err(dev, "%s: ufs_qcom_phy_remove failed = %d\n",
> +			__func__, err);
> +
> +	return err;
> +}
> +
> +static const struct of_device_id ufs_qcom_phy_qmp_20nm_of_match[] = {
> +	{.compatible = "qcom,ufs-phy-qmp-20nm"},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_20nm_of_match);
> +
> +static struct platform_driver ufs_qcom_phy_qmp_20nm_driver = {
> +	.probe = ufs_qcom_phy_qmp_20nm_probe,
> +	.remove = ufs_qcom_phy_qmp_20nm_remove,
> +	.driver = {
> +		.of_match_table = ufs_qcom_phy_qmp_20nm_of_match,
> +		.name = "ufs_qcom_phy_qmp_20nm",
> +		.owner = THIS_MODULE,
> +	},
> +};
> +
> +module_platform_driver(ufs_qcom_phy_qmp_20nm_driver);
> +
> +MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 20nm");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.h
> b/drivers/phy/phy-qcom-ufs-qmp-20nm.h
> new file mode 100644
> index 0000000..4f3076b
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs-qmp-20nm.h
> @@ -0,0 +1,235 @@
> +/*
> + * Copyright (c) 2013-2015, 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.
> + *
> + */
> +
> +#ifndef UFS_QCOM_PHY_QMP_20NM_H_
> +#define UFS_QCOM_PHY_QMP_20NM_H_
> +
> +#include "phy-qcom-ufs-i.h"
> +
> +/* QCOM UFS PHY control registers */
> +
> +#define COM_OFF(x)     (0x000 + x)
> +#define PHY_OFF(x)     (0xC00 + x)
> +#define TX_OFF(n, x)   (0x400 + (0x400 * n) + x)
> +#define RX_OFF(n, x)   (0x600 + (0x400 * n) + x)
> +
> +/* UFS PHY PLL block registers */
> +#define QSERDES_COM_SYS_CLK_CTRL		COM_OFF(0x0)
> +#define QSERDES_COM_PLL_VCOTAIL_EN		COM_OFF(0x04)
> +#define QSERDES_COM_PLL_CNTRL			COM_OFF(0x14)
> +#define QSERDES_COM_PLL_IP_SETI			COM_OFF(0x24)
> +#define QSERDES_COM_CORE_CLK_IN_SYNC_SEL	COM_OFF(0x28)
> +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN		COM_OFF(0x30)
> +#define QSERDES_COM_PLL_CP_SETI			COM_OFF(0x34)
> +#define QSERDES_COM_PLL_IP_SETP			COM_OFF(0x38)
> +#define QSERDES_COM_PLL_CP_SETP			COM_OFF(0x3C)
> +#define QSERDES_COM_SYSCLK_EN_SEL_TXBAND	COM_OFF(0x48)
> +#define QSERDES_COM_RESETSM_CNTRL		COM_OFF(0x4C)
> +#define QSERDES_COM_RESETSM_CNTRL2		COM_OFF(0x50)
> +#define QSERDES_COM_PLLLOCK_CMP1		COM_OFF(0x90)
> +#define QSERDES_COM_PLLLOCK_CMP2		COM_OFF(0x94)
> +#define QSERDES_COM_PLLLOCK_CMP3		COM_OFF(0x98)
> +#define QSERDES_COM_PLLLOCK_CMP_EN		COM_OFF(0x9C)
> +#define QSERDES_COM_BGTC			COM_OFF(0xA0)
> +#define QSERDES_COM_DEC_START1			COM_OFF(0xAC)
> +#define QSERDES_COM_PLL_AMP_OS			COM_OFF(0xB0)
> +#define QSERDES_COM_RES_CODE_UP_OFFSET		COM_OFF(0xD8)
> +#define QSERDES_COM_RES_CODE_DN_OFFSET		COM_OFF(0xDC)
> +#define QSERDES_COM_DIV_FRAC_START1		COM_OFF(0x100)
> +#define QSERDES_COM_DIV_FRAC_START2		COM_OFF(0x104)
> +#define QSERDES_COM_DIV_FRAC_START3		COM_OFF(0x108)
> +#define QSERDES_COM_DEC_START2			COM_OFF(0x10C)
> +#define QSERDES_COM_PLL_RXTXEPCLK_EN		COM_OFF(0x110)
> +#define QSERDES_COM_PLL_CRCTRL			COM_OFF(0x114)
> +#define QSERDES_COM_PLL_CLKEPDIV		COM_OFF(0x118)
> +
> +/* TX LANE n (0, 1) registers */
> +#define QSERDES_TX_EMP_POST1_LVL(n)		TX_OFF(n, 0x08)
> +#define QSERDES_TX_DRV_LVL(n)			TX_OFF(n, 0x0C)
> +#define QSERDES_TX_LANE_MODE(n)			TX_OFF(n, 0x54)
> +
> +/* RX LANE n (0, 1) registers */
> +#define QSERDES_RX_CDR_CONTROL1(n)		RX_OFF(n, 0x0)
> +#define QSERDES_RX_CDR_CONTROL_HALF(n)		RX_OFF(n, 0x8)
> +#define QSERDES_RX_RX_EQ_GAIN1_LSB(n)		RX_OFF(n, 0xA8)
> +#define QSERDES_RX_RX_EQ_GAIN1_MSB(n)		RX_OFF(n, 0xAC)
> +#define QSERDES_RX_RX_EQ_GAIN2_LSB(n)		RX_OFF(n, 0xB0)
> +#define QSERDES_RX_RX_EQ_GAIN2_MSB(n)		RX_OFF(n, 0xB4)
> +#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(n)	RX_OFF(n, 0xBC)
> +#define QSERDES_RX_CDR_CONTROL_QUARTER(n)	RX_OFF(n, 0xC)
> +#define QSERDES_RX_SIGDET_CNTRL(n)		RX_OFF(n, 0x100)
> +
> +/* UFS PHY registers */
> +#define UFS_PHY_PHY_START			PHY_OFF(0x00)
> +#define UFS_PHY_POWER_DOWN_CONTROL		PHY_OFF(0x4)
> +#define UFS_PHY_TX_LANE_ENABLE			PHY_OFF(0x44)
> +#define UFS_PHY_PWM_G1_CLK_DIVIDER		PHY_OFF(0x08)
> +#define UFS_PHY_PWM_G2_CLK_DIVIDER		PHY_OFF(0x0C)
> +#define UFS_PHY_PWM_G3_CLK_DIVIDER		PHY_OFF(0x10)
> +#define UFS_PHY_PWM_G4_CLK_DIVIDER		PHY_OFF(0x14)
> +#define UFS_PHY_CORECLK_PWM_G1_CLK_DIVIDER	PHY_OFF(0x34)
> +#define UFS_PHY_CORECLK_PWM_G2_CLK_DIVIDER	PHY_OFF(0x38)
> +#define UFS_PHY_CORECLK_PWM_G3_CLK_DIVIDER	PHY_OFF(0x3C)
> +#define UFS_PHY_CORECLK_PWM_G4_CLK_DIVIDER	PHY_OFF(0x40)
> +#define UFS_PHY_OMC_STATUS_RDVAL		PHY_OFF(0x68)
> +#define UFS_PHY_LINE_RESET_TIME			PHY_OFF(0x28)
> +#define UFS_PHY_LINE_RESET_GRANULARITY		PHY_OFF(0x2C)
> +#define UFS_PHY_TSYNC_RSYNC_CNTL		PHY_OFF(0x48)
> +#define UFS_PHY_PLL_CNTL			PHY_OFF(0x50)
> +#define UFS_PHY_TX_LARGE_AMP_DRV_LVL		PHY_OFF(0x54)
> +#define UFS_PHY_TX_SMALL_AMP_DRV_LVL		PHY_OFF(0x5C)
> +#define UFS_PHY_TX_LARGE_AMP_POST_EMP_LVL	PHY_OFF(0x58)
> +#define UFS_PHY_TX_SMALL_AMP_POST_EMP_LVL	PHY_OFF(0x60)
> +#define UFS_PHY_CFG_CHANGE_CNT_VAL		PHY_OFF(0x64)
> +#define UFS_PHY_RX_SYNC_WAIT_TIME		PHY_OFF(0x6C)
> +#define UFS_PHY_TX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xB4)
> +#define UFS_PHY_RX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xE0)
> +#define UFS_PHY_TX_MIN_STALL_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xB8)
> +#define UFS_PHY_RX_MIN_STALL_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xE4)
> +#define UFS_PHY_TX_MIN_SAVE_CONFIG_TIME_CAPABILITY	PHY_OFF(0xBC)
> +#define UFS_PHY_RX_MIN_SAVE_CONFIG_TIME_CAPABILITY	PHY_OFF(0xE8)
> +#define UFS_PHY_RX_PWM_BURST_CLOSURE_LENGTH_CAPABILITY	PHY_OFF(0xFC)
> +#define UFS_PHY_RX_MIN_ACTIVATETIME_CAPABILITY		PHY_OFF(0x100)
> +#define UFS_PHY_RX_SIGDET_CTRL3
> PHY_OFF(0x14c)
> +#define UFS_PHY_RMMI_ATTR_CTRL			PHY_OFF(0x160)
> +#define UFS_PHY_RMMI_RX_CFGUPDT_L1	(1 << 7)
> +#define UFS_PHY_RMMI_TX_CFGUPDT_L1	(1 << 6)
> +#define UFS_PHY_RMMI_CFGWR_L1		(1 << 5)
> +#define UFS_PHY_RMMI_CFGRD_L1		(1 << 4)
> +#define UFS_PHY_RMMI_RX_CFGUPDT_L0	(1 << 3)
> +#define UFS_PHY_RMMI_TX_CFGUPDT_L0	(1 << 2)
> +#define UFS_PHY_RMMI_CFGWR_L0		(1 << 1)
> +#define UFS_PHY_RMMI_CFGRD_L0		(1 << 0)
> +#define UFS_PHY_RMMI_ATTRID			PHY_OFF(0x164)
> +#define UFS_PHY_RMMI_ATTRWRVAL			PHY_OFF(0x168)
> +#define UFS_PHY_RMMI_ATTRRDVAL_L0_STATUS	PHY_OFF(0x16C)
> +#define UFS_PHY_RMMI_ATTRRDVAL_L1_STATUS	PHY_OFF(0x170)
> +#define UFS_PHY_PCS_READY_STATUS		PHY_OFF(0x174)
> +
> +#define UFS_PHY_TX_LANE_ENABLE_MASK		0x3
> +
> +/*
> + * This structure represents the 20nm specific phy.
> + * common_cfg MUST remain the first field in this structure
> + * in case extra fields are added. This way, when calling
> + * get_ufs_qcom_phy() of generic phy, we can extract the
> + * common phy structure (struct ufs_qcom_phy) out of it
> + * regardless of the relevant specific phy.
> + */
> +struct ufs_qcom_phy_qmp_20nm {
> +	struct ufs_qcom_phy common_cfg;
> +};
> +
> +static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_2_0[] = {
> +	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x3f),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x1b),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x0f),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(0), 0x2F),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(0), 0x20),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(1), 0x2F),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(1), 0x20),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3),
> +};
> +
> +static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_3_0[] = {
> +	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x2b),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x38),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x3c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_UP_OFFSET, 0x02),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_DN_OFFSET, 0x02),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CNTRL, 0x40),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3),
> +};
> +
> +static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x98),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0x65),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x1e),
> +};
> +
> +#endif
> diff --git a/include/linux/phy/phy-qcom-ufs.h
> b/include/linux/phy/phy-qcom-ufs.h
> new file mode 100644
> index 0000000..9d18e9f
> --- /dev/null
> +++ b/include/linux/phy/phy-qcom-ufs.h
> @@ -0,0 +1,59 @@
> +/*
> + * Copyright (c) 2013-2015, 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.
> + *
> + */
> +
> +#ifndef PHY_QCOM_UFS_H_
> +#define PHY_QCOM_UFS_H_
> +
> +#include "phy.h"
> +
> +/**
> + * ufs_qcom_phy_enable_ref_clk() - Enable the phy
> + * ref clock.
> + * @phy: reference to a generic phy
> + *
> + * returns 0 for success, and non-zero for error.
> + */
> +int ufs_qcom_phy_enable_ref_clk(struct phy *phy);
> +
> +/**
> + * ufs_qcom_phy_disable_ref_clk() - Disable the phy
> + * ref clock.
> + * @phy: reference to a generic phy.
> + */
> +void ufs_qcom_phy_disable_ref_clk(struct phy *phy);
> +
> +/**
> + * ufs_qcom_phy_enable_dev_ref_clk() - Enable the device
> + * ref clock.
> + * @phy: reference to a generic phy.
> + */
> +void ufs_qcom_phy_enable_dev_ref_clk(struct phy *phy);
> +
> +/**
> + * ufs_qcom_phy_disable_dev_ref_clk() - Disable the device
> + * ref clock.
> + * @phy: reference to a generic phy.
> + */
> +void ufs_qcom_phy_disable_dev_ref_clk(struct phy *phy);
> +
> +int ufs_qcom_phy_enable_iface_clk(struct phy *phy);
> +void ufs_qcom_phy_disable_iface_clk(struct phy *phy);
> +int ufs_qcom_phy_start_serdes(struct phy *phy);
> +int ufs_qcom_phy_set_tx_lane_enable(struct phy *phy, u32 tx_lanes);
> +int ufs_qcom_phy_calibrate_phy(struct phy *phy, bool is_rate_B);
> +int ufs_qcom_phy_is_pcs_ready(struct phy *phy);
> +void ufs_qcom_phy_save_controller_version(struct phy *phy,
> +			u8 major, u16 minor, u16 step);
> +
> +#endif /* PHY_QCOM_UFS_H_ */
> --
> 1.8.5.2
>
> --
> QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
> of Code Aurora Forum, hosted by The Linux Foundation
> --
> To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v7 2/5] phy: qcom-ufs: add support for 20nm phy
@ 2015-01-15 15:18     ` dovl
  0 siblings, 0 replies; 27+ messages in thread
From: dovl @ 2015-01-15 15:18 UTC (permalink / raw)
  To: Yaniv Gardi
  Cc: james.bottomley, hch, linux-kernel, linux-scsi, linux-arm-msm,
	santoshsy, linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Kishon Vijay Abraham I, Grant Likely, Rob Herring,
	open list:OPEN FIRMWARE AND...

Reviewed-by: Dov Levenglick <dovl@codeaurora.org>

> This change adds a support for a 20nm qcom-ufs phy that is required in
> platforms that use ufs-qcom controller.
>
> Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>
>
> ---
>  drivers/phy/Makefile                |   1 +
>  drivers/phy/phy-qcom-ufs-i.h        |  43 +++++-
>  drivers/phy/phy-qcom-ufs-qmp-20nm.c | 257
> ++++++++++++++++++++++++++++++++++++
>  drivers/phy/phy-qcom-ufs-qmp-20nm.h | 235
> +++++++++++++++++++++++++++++++++
>  include/linux/phy/phy-qcom-ufs.h    |  59 +++++++++
>  5 files changed, 594 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.c
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.h
>  create mode 100644 include/linux/phy/phy-qcom-ufs.h
>
> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
> index 335965d..781b2fa 100644
> --- a/drivers/phy/Makefile
> +++ b/drivers/phy/Makefile
> @@ -35,3 +35,4 @@ obj-$(CONFIG_PHY_XGENE)			+=
> phy-xgene.o
>  obj-$(CONFIG_PHY_STIH407_USB)		+= phy-stih407-usb.o
>  obj-$(CONFIG_PHY_STIH41X_USB)		+= phy-stih41x-usb.o
>  obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs.o
> +obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs-qmp-20nm.o
> diff --git a/drivers/phy/phy-qcom-ufs-i.h b/drivers/phy/phy-qcom-ufs-i.h
> index dac200f..591a391 100644
> --- a/drivers/phy/phy-qcom-ufs-i.h
> +++ b/drivers/phy/phy-qcom-ufs-i.h
> @@ -15,15 +15,56 @@
>  #ifndef UFS_QCOM_PHY_I_H_
>  #define UFS_QCOM_PHY_I_H_
>
> +#include <linux/module.h>
>  #include <linux/clk.h>
> +#include <linux/regulator/consumer.h>
>  #include <linux/slab.h>
> -#include <linux/phy/phy.h>
> +#include <linux/phy/phy-qcom-ufs.h>
>  #include <linux/platform_device.h>
>  #include <linux/io.h>
>  #include <linux/delay.h>
>
> +#define readl_poll_timeout(addr, val, cond, sleep_us, timeout_us) \
> +({ \
> +	ktime_t timeout = ktime_add_us(ktime_get(), timeout_us); \
> +	might_sleep_if(timeout_us); \
> +	for (;;) { \
> +		(val) = readl(addr); \
> +		if (cond) \
> +			break; \
> +		if (timeout_us && ktime_compare(ktime_get(), timeout) > 0)
> { \
> +			(val) = readl(addr); \
> +			break; \
> +		} \
> +		if (sleep_us) \
> +			usleep_range(DIV_ROUND_UP(sleep_us, 4), sleep_us);
> \
> +	} \
> +	(cond) ? 0 : -ETIMEDOUT; \
> +})
> +
> +#define UFS_QCOM_PHY_CAL_ENTRY(reg, val)	\
> +	{				\
> +		.reg_offset = reg,	\
> +		.cfg_value = val,	\
> +	}
> +
>  #define UFS_QCOM_PHY_NAME_LEN	30
>
> +enum {
> +	MASK_SERDES_START       = 0x1,
> +	MASK_PCS_READY          = 0x1,
> +};
> +
> +enum {
> +	OFFSET_SERDES_START     = 0x0,
> +};
> +
> +struct ufs_qcom_phy_stored_attributes {
> +	u32 att;
> +	u32 value;
> +};
> +
> +
>  struct ufs_qcom_phy_calibration {
>  	u32 reg_offset;
>  	u32 cfg_value;
> diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.c
> b/drivers/phy/phy-qcom-ufs-qmp-20nm.c
> new file mode 100644
> index 0000000..8332f96
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs-qmp-20nm.c
> @@ -0,0 +1,257 @@
> +/*
> + * Copyright (c) 2013-2015, 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 "phy-qcom-ufs-qmp-20nm.h"
> +
> +#define UFS_PHY_NAME "ufs_phy_qmp_20nm"
> +
> +static
> +int ufs_qcom_phy_qmp_20nm_phy_calibrate(struct ufs_qcom_phy
> *ufs_qcom_phy,
> +					bool is_rate_B)
> +{
> +	struct ufs_qcom_phy_calibration *tbl_A, *tbl_B;
> +	int tbl_size_A, tbl_size_B;
> +	u8 major = ufs_qcom_phy->host_ctrl_rev_major;
> +	u16 minor = ufs_qcom_phy->host_ctrl_rev_minor;
> +	u16 step = ufs_qcom_phy->host_ctrl_rev_step;
> +	int err;
> +
> +	if ((major == 0x1) && (minor == 0x002) && (step == 0x0000)) {
> +		tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_2_0);
> +		tbl_A = phy_cal_table_rate_A_1_2_0;
> +	} else if ((major == 0x1) && (minor == 0x003) && (step == 0x0000))
> {
> +		tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_3_0);
> +		tbl_A = phy_cal_table_rate_A_1_3_0;
> +	} else {
> +		dev_err(ufs_qcom_phy->dev, "%s: Unknown UFS-PHY version,
> no calibration values\n",
> +			__func__);
> +		err = -ENODEV;
> +		goto out;
> +	}
> +
> +	tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
> +	tbl_B = phy_cal_table_rate_B;
> +
> +	err = ufs_qcom_phy_calibrate(ufs_qcom_phy, tbl_A, tbl_size_A,
> +						tbl_B, tbl_size_B,
> is_rate_B);
> +
> +	if (err)
> +		dev_err(ufs_qcom_phy->dev, "%s: ufs_qcom_phy_calibrate()
> failed %d\n",
> +			__func__, err);
> +
> +out:
> +	return err;
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_20nm_advertise_quirks(struct ufs_qcom_phy
> *phy_common)
> +{
> +	phy_common->quirks =
> +		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
> +}
> +
> +static int ufs_qcom_phy_qmp_20nm_init(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy_qmp_20nm *phy = phy_get_drvdata(generic_phy);
> +	struct ufs_qcom_phy *phy_common = &phy->common_cfg;
> +	int err = 0;
> +
> +	err = ufs_qcom_phy_init_clks(generic_phy, phy_common);
> +	if (err) {
> +		dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_clks()
> failed %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	err = ufs_qcom_phy_init_vregulators(generic_phy, phy_common);
> +	if (err) {
> +		dev_err(phy_common->dev, "%s:
> ufs_qcom_phy_init_vregulators() failed %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	ufs_qcom_phy_qmp_20nm_advertise_quirks(phy_common);
> +
> +out:
> +	return err;
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_20nm_power_control(struct ufs_qcom_phy *phy, bool
> val)
> +{
> +	bool hibern8_exit_after_pwr_collapse = phy->quirks &
> +		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
> +
> +	if (val) {
> +		writel_relaxed(0x1, phy->mmio +
> UFS_PHY_POWER_DOWN_CONTROL);
> +		/*
> +		 * Before any transactions involving PHY, ensure PHY knows
> +		 * that it's analog rail is powered ON.
> +		 */
> +		mb();
> +
> +		if (hibern8_exit_after_pwr_collapse) {
> +			/*
> +			 * Give atleast 1us delay after restoring PHY
> analog
> +			 * power.
> +			 */
> +			usleep_range(1, 2);
> +			writel_relaxed(0x0A, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			writel_relaxed(0x08, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			/*
> +			 * Make sure workaround is deactivated before
> proceeding
> +			 * with normal PHY operations.
> +			 */
> +			mb();
> +		}
> +	} else {
> +		if (hibern8_exit_after_pwr_collapse) {
> +			writel_relaxed(0x0A, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			writel_relaxed(0x02, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			/*
> +			 * Make sure that above workaround is activated
> before
> +			 * PHY analog power collapse.
> +			 */
> +			mb();
> +		}
> +
> +		writel_relaxed(0x0, phy->mmio +
> UFS_PHY_POWER_DOWN_CONTROL);
> +		/*
> +		 * ensure that PHY knows its PHY analog rail is going
> +		 * to be powered down
> +		 */
> +		mb();
> +	}
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_20nm_set_tx_lane_enable(struct ufs_qcom_phy *phy,
> u32 val)
> +{
> +	writel_relaxed(val & UFS_PHY_TX_LANE_ENABLE_MASK,
> +			phy->mmio + UFS_PHY_TX_LANE_ENABLE);
> +	mb();
> +}
> +
> +static inline void ufs_qcom_phy_qmp_20nm_start_serdes(struct ufs_qcom_phy
> *phy)
> +{
> +	u32 tmp;
> +
> +	tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
> +	tmp &= ~MASK_SERDES_START;
> +	tmp |= (1 << OFFSET_SERDES_START);
> +	writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
> +	mb();
> +}
> +
> +static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy
> *phy_common)
> +{
> +	int err = 0;
> +	u32 val;
> +
> +	err = readl_poll_timeout(phy_common->mmio +
> UFS_PHY_PCS_READY_STATUS,
> +			val, (val & MASK_PCS_READY), 10, 1000000);
> +	if (err)
> +		dev_err(phy_common->dev, "%s: poll for pcs failed err =
> %d\n",
> +			__func__, err);
> +	return err;
> +}
> +
> +static struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
> +	.init		= ufs_qcom_phy_qmp_20nm_init,
> +	.exit		= ufs_qcom_phy_exit,
> +	.power_on	= ufs_qcom_phy_power_on,
> +	.power_off	= ufs_qcom_phy_power_off,
> +	.owner		= THIS_MODULE,
> +};
> +
> +static struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
> +	.calibrate_phy		= ufs_qcom_phy_qmp_20nm_phy_calibrate,
> +	.start_serdes		= ufs_qcom_phy_qmp_20nm_start_serdes,
> +	.is_physical_coding_sublayer_ready =
> ufs_qcom_phy_qmp_20nm_is_pcs_ready,
> +	.set_tx_lane_enable	=
> ufs_qcom_phy_qmp_20nm_set_tx_lane_enable,
> +	.power_control		= ufs_qcom_phy_qmp_20nm_power_control,
> +};
> +
> +static int ufs_qcom_phy_qmp_20nm_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct phy *generic_phy;
> +	struct ufs_qcom_phy_qmp_20nm *phy;
> +	int err = 0;
> +
> +	phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
> +	if (!phy) {
> +		dev_err(dev, "%s: failed to allocate phy\n", __func__);
> +		err = -ENOMEM;
> +		goto out;
> +	}
> +
> +	generic_phy = ufs_qcom_phy_generic_probe(pdev, &phy->common_cfg,
> +				&ufs_qcom_phy_qmp_20nm_phy_ops,
> &phy_20nm_ops);
> +
> +	if (!generic_phy) {
> +		dev_err(dev, "%s: ufs_qcom_phy_generic_probe() failed\n",
> +			__func__);
> +		err = -EIO;
> +		goto out;
> +	}
> +
> +	phy_set_drvdata(generic_phy, phy);
> +
> +	strlcpy(phy->common_cfg.name, UFS_PHY_NAME,
> +			sizeof(phy->common_cfg.name));
> +
> +out:
> +	return err;
> +}
> +
> +static int ufs_qcom_phy_qmp_20nm_remove(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct phy *generic_phy = to_phy(dev);
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +	int err = 0;
> +
> +	err = ufs_qcom_phy_remove(generic_phy, ufs_qcom_phy);
> +	if (err)
> +		dev_err(dev, "%s: ufs_qcom_phy_remove failed = %d\n",
> +			__func__, err);
> +
> +	return err;
> +}
> +
> +static const struct of_device_id ufs_qcom_phy_qmp_20nm_of_match[] = {
> +	{.compatible = "qcom,ufs-phy-qmp-20nm"},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_20nm_of_match);
> +
> +static struct platform_driver ufs_qcom_phy_qmp_20nm_driver = {
> +	.probe = ufs_qcom_phy_qmp_20nm_probe,
> +	.remove = ufs_qcom_phy_qmp_20nm_remove,
> +	.driver = {
> +		.of_match_table = ufs_qcom_phy_qmp_20nm_of_match,
> +		.name = "ufs_qcom_phy_qmp_20nm",
> +		.owner = THIS_MODULE,
> +	},
> +};
> +
> +module_platform_driver(ufs_qcom_phy_qmp_20nm_driver);
> +
> +MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 20nm");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.h
> b/drivers/phy/phy-qcom-ufs-qmp-20nm.h
> new file mode 100644
> index 0000000..4f3076b
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs-qmp-20nm.h
> @@ -0,0 +1,235 @@
> +/*
> + * Copyright (c) 2013-2015, 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.
> + *
> + */
> +
> +#ifndef UFS_QCOM_PHY_QMP_20NM_H_
> +#define UFS_QCOM_PHY_QMP_20NM_H_
> +
> +#include "phy-qcom-ufs-i.h"
> +
> +/* QCOM UFS PHY control registers */
> +
> +#define COM_OFF(x)     (0x000 + x)
> +#define PHY_OFF(x)     (0xC00 + x)
> +#define TX_OFF(n, x)   (0x400 + (0x400 * n) + x)
> +#define RX_OFF(n, x)   (0x600 + (0x400 * n) + x)
> +
> +/* UFS PHY PLL block registers */
> +#define QSERDES_COM_SYS_CLK_CTRL		COM_OFF(0x0)
> +#define QSERDES_COM_PLL_VCOTAIL_EN		COM_OFF(0x04)
> +#define QSERDES_COM_PLL_CNTRL			COM_OFF(0x14)
> +#define QSERDES_COM_PLL_IP_SETI			COM_OFF(0x24)
> +#define QSERDES_COM_CORE_CLK_IN_SYNC_SEL	COM_OFF(0x28)
> +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN		COM_OFF(0x30)
> +#define QSERDES_COM_PLL_CP_SETI			COM_OFF(0x34)
> +#define QSERDES_COM_PLL_IP_SETP			COM_OFF(0x38)
> +#define QSERDES_COM_PLL_CP_SETP			COM_OFF(0x3C)
> +#define QSERDES_COM_SYSCLK_EN_SEL_TXBAND	COM_OFF(0x48)
> +#define QSERDES_COM_RESETSM_CNTRL		COM_OFF(0x4C)
> +#define QSERDES_COM_RESETSM_CNTRL2		COM_OFF(0x50)
> +#define QSERDES_COM_PLLLOCK_CMP1		COM_OFF(0x90)
> +#define QSERDES_COM_PLLLOCK_CMP2		COM_OFF(0x94)
> +#define QSERDES_COM_PLLLOCK_CMP3		COM_OFF(0x98)
> +#define QSERDES_COM_PLLLOCK_CMP_EN		COM_OFF(0x9C)
> +#define QSERDES_COM_BGTC			COM_OFF(0xA0)
> +#define QSERDES_COM_DEC_START1			COM_OFF(0xAC)
> +#define QSERDES_COM_PLL_AMP_OS			COM_OFF(0xB0)
> +#define QSERDES_COM_RES_CODE_UP_OFFSET		COM_OFF(0xD8)
> +#define QSERDES_COM_RES_CODE_DN_OFFSET		COM_OFF(0xDC)
> +#define QSERDES_COM_DIV_FRAC_START1		COM_OFF(0x100)
> +#define QSERDES_COM_DIV_FRAC_START2		COM_OFF(0x104)
> +#define QSERDES_COM_DIV_FRAC_START3		COM_OFF(0x108)
> +#define QSERDES_COM_DEC_START2			COM_OFF(0x10C)
> +#define QSERDES_COM_PLL_RXTXEPCLK_EN		COM_OFF(0x110)
> +#define QSERDES_COM_PLL_CRCTRL			COM_OFF(0x114)
> +#define QSERDES_COM_PLL_CLKEPDIV		COM_OFF(0x118)
> +
> +/* TX LANE n (0, 1) registers */
> +#define QSERDES_TX_EMP_POST1_LVL(n)		TX_OFF(n, 0x08)
> +#define QSERDES_TX_DRV_LVL(n)			TX_OFF(n, 0x0C)
> +#define QSERDES_TX_LANE_MODE(n)			TX_OFF(n, 0x54)
> +
> +/* RX LANE n (0, 1) registers */
> +#define QSERDES_RX_CDR_CONTROL1(n)		RX_OFF(n, 0x0)
> +#define QSERDES_RX_CDR_CONTROL_HALF(n)		RX_OFF(n, 0x8)
> +#define QSERDES_RX_RX_EQ_GAIN1_LSB(n)		RX_OFF(n, 0xA8)
> +#define QSERDES_RX_RX_EQ_GAIN1_MSB(n)		RX_OFF(n, 0xAC)
> +#define QSERDES_RX_RX_EQ_GAIN2_LSB(n)		RX_OFF(n, 0xB0)
> +#define QSERDES_RX_RX_EQ_GAIN2_MSB(n)		RX_OFF(n, 0xB4)
> +#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(n)	RX_OFF(n, 0xBC)
> +#define QSERDES_RX_CDR_CONTROL_QUARTER(n)	RX_OFF(n, 0xC)
> +#define QSERDES_RX_SIGDET_CNTRL(n)		RX_OFF(n, 0x100)
> +
> +/* UFS PHY registers */
> +#define UFS_PHY_PHY_START			PHY_OFF(0x00)
> +#define UFS_PHY_POWER_DOWN_CONTROL		PHY_OFF(0x4)
> +#define UFS_PHY_TX_LANE_ENABLE			PHY_OFF(0x44)
> +#define UFS_PHY_PWM_G1_CLK_DIVIDER		PHY_OFF(0x08)
> +#define UFS_PHY_PWM_G2_CLK_DIVIDER		PHY_OFF(0x0C)
> +#define UFS_PHY_PWM_G3_CLK_DIVIDER		PHY_OFF(0x10)
> +#define UFS_PHY_PWM_G4_CLK_DIVIDER		PHY_OFF(0x14)
> +#define UFS_PHY_CORECLK_PWM_G1_CLK_DIVIDER	PHY_OFF(0x34)
> +#define UFS_PHY_CORECLK_PWM_G2_CLK_DIVIDER	PHY_OFF(0x38)
> +#define UFS_PHY_CORECLK_PWM_G3_CLK_DIVIDER	PHY_OFF(0x3C)
> +#define UFS_PHY_CORECLK_PWM_G4_CLK_DIVIDER	PHY_OFF(0x40)
> +#define UFS_PHY_OMC_STATUS_RDVAL		PHY_OFF(0x68)
> +#define UFS_PHY_LINE_RESET_TIME			PHY_OFF(0x28)
> +#define UFS_PHY_LINE_RESET_GRANULARITY		PHY_OFF(0x2C)
> +#define UFS_PHY_TSYNC_RSYNC_CNTL		PHY_OFF(0x48)
> +#define UFS_PHY_PLL_CNTL			PHY_OFF(0x50)
> +#define UFS_PHY_TX_LARGE_AMP_DRV_LVL		PHY_OFF(0x54)
> +#define UFS_PHY_TX_SMALL_AMP_DRV_LVL		PHY_OFF(0x5C)
> +#define UFS_PHY_TX_LARGE_AMP_POST_EMP_LVL	PHY_OFF(0x58)
> +#define UFS_PHY_TX_SMALL_AMP_POST_EMP_LVL	PHY_OFF(0x60)
> +#define UFS_PHY_CFG_CHANGE_CNT_VAL		PHY_OFF(0x64)
> +#define UFS_PHY_RX_SYNC_WAIT_TIME		PHY_OFF(0x6C)
> +#define UFS_PHY_TX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xB4)
> +#define UFS_PHY_RX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xE0)
> +#define UFS_PHY_TX_MIN_STALL_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xB8)
> +#define UFS_PHY_RX_MIN_STALL_NOCONFIG_TIME_CAPABILITY	PHY_OFF(0xE4)
> +#define UFS_PHY_TX_MIN_SAVE_CONFIG_TIME_CAPABILITY	PHY_OFF(0xBC)
> +#define UFS_PHY_RX_MIN_SAVE_CONFIG_TIME_CAPABILITY	PHY_OFF(0xE8)
> +#define UFS_PHY_RX_PWM_BURST_CLOSURE_LENGTH_CAPABILITY	PHY_OFF(0xFC)
> +#define UFS_PHY_RX_MIN_ACTIVATETIME_CAPABILITY		PHY_OFF(0x100)
> +#define UFS_PHY_RX_SIGDET_CTRL3
> PHY_OFF(0x14c)
> +#define UFS_PHY_RMMI_ATTR_CTRL			PHY_OFF(0x160)
> +#define UFS_PHY_RMMI_RX_CFGUPDT_L1	(1 << 7)
> +#define UFS_PHY_RMMI_TX_CFGUPDT_L1	(1 << 6)
> +#define UFS_PHY_RMMI_CFGWR_L1		(1 << 5)
> +#define UFS_PHY_RMMI_CFGRD_L1		(1 << 4)
> +#define UFS_PHY_RMMI_RX_CFGUPDT_L0	(1 << 3)
> +#define UFS_PHY_RMMI_TX_CFGUPDT_L0	(1 << 2)
> +#define UFS_PHY_RMMI_CFGWR_L0		(1 << 1)
> +#define UFS_PHY_RMMI_CFGRD_L0		(1 << 0)
> +#define UFS_PHY_RMMI_ATTRID			PHY_OFF(0x164)
> +#define UFS_PHY_RMMI_ATTRWRVAL			PHY_OFF(0x168)
> +#define UFS_PHY_RMMI_ATTRRDVAL_L0_STATUS	PHY_OFF(0x16C)
> +#define UFS_PHY_RMMI_ATTRRDVAL_L1_STATUS	PHY_OFF(0x170)
> +#define UFS_PHY_PCS_READY_STATUS		PHY_OFF(0x174)
> +
> +#define UFS_PHY_TX_LANE_ENABLE_MASK		0x3
> +
> +/*
> + * This structure represents the 20nm specific phy.
> + * common_cfg MUST remain the first field in this structure
> + * in case extra fields are added. This way, when calling
> + * get_ufs_qcom_phy() of generic phy, we can extract the
> + * common phy structure (struct ufs_qcom_phy) out of it
> + * regardless of the relevant specific phy.
> + */
> +struct ufs_qcom_phy_qmp_20nm {
> +	struct ufs_qcom_phy common_cfg;
> +};
> +
> +static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_2_0[] = {
> +	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x3f),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x1b),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x0f),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(0), 0x2F),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(0), 0x20),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(1), 0x2F),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(1), 0x20),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3),
> +};
> +
> +static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_3_0[] = {
> +	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x2b),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x38),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x3c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_UP_OFFSET, 0x02),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_DN_OFFSET, 0x02),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CNTRL, 0x40),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3),
> +};
> +
> +static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x98),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0x65),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x1e),
> +};
> +
> +#endif
> diff --git a/include/linux/phy/phy-qcom-ufs.h
> b/include/linux/phy/phy-qcom-ufs.h
> new file mode 100644
> index 0000000..9d18e9f
> --- /dev/null
> +++ b/include/linux/phy/phy-qcom-ufs.h
> @@ -0,0 +1,59 @@
> +/*
> + * Copyright (c) 2013-2015, 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.
> + *
> + */
> +
> +#ifndef PHY_QCOM_UFS_H_
> +#define PHY_QCOM_UFS_H_
> +
> +#include "phy.h"
> +
> +/**
> + * ufs_qcom_phy_enable_ref_clk() - Enable the phy
> + * ref clock.
> + * @phy: reference to a generic phy
> + *
> + * returns 0 for success, and non-zero for error.
> + */
> +int ufs_qcom_phy_enable_ref_clk(struct phy *phy);
> +
> +/**
> + * ufs_qcom_phy_disable_ref_clk() - Disable the phy
> + * ref clock.
> + * @phy: reference to a generic phy.
> + */
> +void ufs_qcom_phy_disable_ref_clk(struct phy *phy);
> +
> +/**
> + * ufs_qcom_phy_enable_dev_ref_clk() - Enable the device
> + * ref clock.
> + * @phy: reference to a generic phy.
> + */
> +void ufs_qcom_phy_enable_dev_ref_clk(struct phy *phy);
> +
> +/**
> + * ufs_qcom_phy_disable_dev_ref_clk() - Disable the device
> + * ref clock.
> + * @phy: reference to a generic phy.
> + */
> +void ufs_qcom_phy_disable_dev_ref_clk(struct phy *phy);
> +
> +int ufs_qcom_phy_enable_iface_clk(struct phy *phy);
> +void ufs_qcom_phy_disable_iface_clk(struct phy *phy);
> +int ufs_qcom_phy_start_serdes(struct phy *phy);
> +int ufs_qcom_phy_set_tx_lane_enable(struct phy *phy, u32 tx_lanes);
> +int ufs_qcom_phy_calibrate_phy(struct phy *phy, bool is_rate_B);
> +int ufs_qcom_phy_is_pcs_ready(struct phy *phy);
> +void ufs_qcom_phy_save_controller_version(struct phy *phy,
> +			u8 major, u16 minor, u16 step);
> +
> +#endif /* PHY_QCOM_UFS_H_ */
> --
> 1.8.5.2
>
> --
> QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
> of Code Aurora Forum, hosted by The Linux Foundation
> --
> To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>



^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v7 4/5] phy: qcom-ufs: add support for 14nm phy
  2015-01-15 14:32   ` Yaniv Gardi
@ 2015-01-15 15:18     ` dovl
  -1 siblings, 0 replies; 27+ messages in thread
From: dovl @ 2015-01-15 15:18 UTC (permalink / raw)
  Cc: james.bottomley, hch, linux-kernel, linux-scsi, linux-arm-msm,
	santoshsy, linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Kishon Vijay Abraham I, Grant Likely, Rob Herring,
	open list:OPEN FIRMWARE AND...

Reviewed-by: Dov Levenglick <dovl@codeaurora.org>

> This change adds a support for a 14nm qcom-ufs phy that is
> required in platforms that use ufs-qcom controller.
>
> Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>
>
> ---
>  drivers/phy/Makefile                |   1 +
>  drivers/phy/phy-qcom-ufs-qmp-14nm.c | 201
> ++++++++++++++++++++++++++++++++++++
>  drivers/phy/phy-qcom-ufs-qmp-14nm.h | 177 +++++++++++++++++++++++++++++++
>  3 files changed, 379 insertions(+)
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-14nm.c
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-14nm.h
>
> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
> index 781b2fa..cfbb720 100644
> --- a/drivers/phy/Makefile
> +++ b/drivers/phy/Makefile
> @@ -36,3 +36,4 @@ obj-$(CONFIG_PHY_STIH407_USB)		+=
> phy-stih407-usb.o
>  obj-$(CONFIG_PHY_STIH41X_USB)		+= phy-stih41x-usb.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-ufs-qmp-14nm.c
> b/drivers/phy/phy-qcom-ufs-qmp-14nm.c
> new file mode 100644
> index 0000000..f5fc50a
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs-qmp-14nm.c
> @@ -0,0 +1,201 @@
> +/*
> + * Copyright (c) 2013-2015, 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 "phy-qcom-ufs-qmp-14nm.h"
> +
> +#define UFS_PHY_NAME "ufs_phy_qmp_14nm"
> +#define UFS_PHY_VDDA_PHY_UV	(925000)
> +
> +static
> +int ufs_qcom_phy_qmp_14nm_phy_calibrate(struct ufs_qcom_phy
> *ufs_qcom_phy,
> +					bool is_rate_B)
> +{
> +	int tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A);
> +	int tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
> +	int err;
> +
> +	err = ufs_qcom_phy_calibrate(ufs_qcom_phy, phy_cal_table_rate_A,
> +		tbl_size_A, phy_cal_table_rate_B, tbl_size_B, is_rate_B);
> +
> +	if (err)
> +		dev_err(ufs_qcom_phy->dev,
> +			"%s: ufs_qcom_phy_calibrate() failed %d\n",
> +			__func__, err);
> +	return err;
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_14nm_advertise_quirks(struct ufs_qcom_phy
> *phy_common)
> +{
> +	phy_common->quirks =
> +		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
> +}
> +
> +static int ufs_qcom_phy_qmp_14nm_init(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy_qmp_14nm *phy = phy_get_drvdata(generic_phy);
> +	struct ufs_qcom_phy *phy_common = &phy->common_cfg;
> +	int err;
> +
> +	err = ufs_qcom_phy_init_clks(generic_phy, phy_common);
> +	if (err) {
> +		dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_clks()
> failed %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	err = ufs_qcom_phy_init_vregulators(generic_phy, phy_common);
> +	if (err) {
> +		dev_err(phy_common->dev, "%s:
> ufs_qcom_phy_init_vregulators() failed %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +	phy_common->vdda_phy.max_uV = UFS_PHY_VDDA_PHY_UV;
> +	phy_common->vdda_phy.min_uV = UFS_PHY_VDDA_PHY_UV;
> +
> +	ufs_qcom_phy_qmp_14nm_advertise_quirks(phy_common);
> +
> +out:
> +	return err;
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_14nm_power_control(struct ufs_qcom_phy *phy, bool
> val)
> +{
> +	writel_relaxed(val ? 0x1 : 0x0, phy->mmio +
> UFS_PHY_POWER_DOWN_CONTROL);
> +	/*
> +	 * Before any transactions involving PHY, ensure PHY knows
> +	 * that it's analog rail is powered ON (or OFF).
> +	 */
> +	mb();
> +}
> +
> +static inline
> +void ufs_qcom_phy_qmp_14nm_set_tx_lane_enable(struct ufs_qcom_phy *phy,
> u32 val)
> +{
> +	/*
> +	 * 14nm PHY does not have TX_LANE_ENABLE register.
> +	 * Implement this function so as not to propagate error to caller.
> +	 */
> +}
> +
> +static inline void ufs_qcom_phy_qmp_14nm_start_serdes(struct ufs_qcom_phy
> *phy)
> +{
> +	u32 tmp;
> +
> +	tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
> +	tmp &= ~MASK_SERDES_START;
> +	tmp |= (1 << OFFSET_SERDES_START);
> +	writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
> +	/* Ensure register value is committed */
> +	mb();
> +}
> +
> +static int ufs_qcom_phy_qmp_14nm_is_pcs_ready(struct ufs_qcom_phy
> *phy_common)
> +{
> +	int err = 0;
> +	u32 val;
> +
> +	err = readl_poll_timeout(phy_common->mmio +
> UFS_PHY_PCS_READY_STATUS,
> +		val, (val & MASK_PCS_READY), 10, 1000000);
> +	if (err)
> +		dev_err(phy_common->dev, "%s: poll for pcs failed err =
> %d\n",
> +			__func__, err);
> +	return err;
> +}
> +
> +static struct phy_ops ufs_qcom_phy_qmp_14nm_phy_ops = {
> +	.init		= ufs_qcom_phy_qmp_14nm_init,
> +	.exit		= ufs_qcom_phy_exit,
> +	.power_on	= ufs_qcom_phy_power_on,
> +	.power_off	= ufs_qcom_phy_power_off,
> +	.owner		= THIS_MODULE,
> +};
> +
> +static struct ufs_qcom_phy_specific_ops phy_14nm_ops = {
> +	.calibrate_phy		= ufs_qcom_phy_qmp_14nm_phy_calibrate,
> +	.start_serdes		= ufs_qcom_phy_qmp_14nm_start_serdes,
> +	.is_physical_coding_sublayer_ready =
> ufs_qcom_phy_qmp_14nm_is_pcs_ready,
> +	.set_tx_lane_enable	=
> ufs_qcom_phy_qmp_14nm_set_tx_lane_enable,
> +	.power_control		= ufs_qcom_phy_qmp_14nm_power_control,
> +};
> +
> +static int ufs_qcom_phy_qmp_14nm_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct phy *generic_phy;
> +	struct ufs_qcom_phy_qmp_14nm *phy;
> +	int err = 0;
> +
> +	phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
> +	if (!phy) {
> +		dev_err(dev, "%s: failed to allocate phy\n", __func__);
> +		err = -ENOMEM;
> +		goto out;
> +	}
> +
> +	generic_phy = ufs_qcom_phy_generic_probe(pdev, &phy->common_cfg,
> +				&ufs_qcom_phy_qmp_14nm_phy_ops,
> &phy_14nm_ops);
> +
> +	if (!generic_phy) {
> +		dev_err(dev, "%s: ufs_qcom_phy_generic_probe() failed\n",
> +			__func__);
> +		err = -EIO;
> +		goto out;
> +	}
> +
> +	phy_set_drvdata(generic_phy, phy);
> +
> +	strlcpy(phy->common_cfg.name, UFS_PHY_NAME,
> +		sizeof(phy->common_cfg.name));
> +
> +out:
> +	return err;
> +}
> +
> +static int ufs_qcom_phy_qmp_14nm_remove(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct phy *generic_phy = to_phy(dev);
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +	int err = 0;
> +
> +	err = ufs_qcom_phy_remove(generic_phy, ufs_qcom_phy);
> +	if (err)
> +		dev_err(dev, "%s: ufs_qcom_phy_remove failed = %d\n",
> +			__func__, err);
> +
> +	return err;
> +}
> +
> +static const struct of_device_id ufs_qcom_phy_qmp_14nm_of_match[] = {
> +	{.compatible = "qcom,ufs-phy-qmp-14nm"},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_14nm_of_match);
> +
> +static struct platform_driver ufs_qcom_phy_qmp_14nm_driver = {
> +	.probe = ufs_qcom_phy_qmp_14nm_probe,
> +	.remove = ufs_qcom_phy_qmp_14nm_remove,
> +	.driver = {
> +		.of_match_table = ufs_qcom_phy_qmp_14nm_of_match,
> +		.name = "ufs_qcom_phy_qmp_14nm",
> +		.owner = THIS_MODULE,
> +	},
> +};
> +
> +module_platform_driver(ufs_qcom_phy_qmp_14nm_driver);
> +
> +MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 14nm");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/phy/phy-qcom-ufs-qmp-14nm.h
> b/drivers/phy/phy-qcom-ufs-qmp-14nm.h
> new file mode 100644
> index 0000000..3aefdba
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs-qmp-14nm.h
> @@ -0,0 +1,177 @@
> +/*
> + * Copyright (c) 2013-2015, 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.
> + *
> + */
> +
> +#ifndef UFS_QCOM_PHY_QMP_14NM_H_
> +#define UFS_QCOM_PHY_QMP_14NM_H_
> +
> +#include "phy-qcom-ufs-i.h"
> +
> +/* QCOM UFS PHY control registers */
> +#define COM_OFF(x)	(0x000 + x)
> +#define PHY_OFF(x)	(0xC00 + x)
> +#define TX_OFF(n, x)	(0x400 + (0x400 * n) + x)
> +#define RX_OFF(n, x)	(0x600 + (0x400 * n) + x)
> +
> +/* UFS PHY QSERDES COM registers */
> +#define QSERDES_COM_BG_TIMER			COM_OFF(0x0C)
> +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN		COM_OFF(0x34)
> +#define QSERDES_COM_SYS_CLK_CTRL		COM_OFF(0x3C)
> +#define QSERDES_COM_LOCK_CMP1_MODE0		COM_OFF(0x4C)
> +#define QSERDES_COM_LOCK_CMP2_MODE0		COM_OFF(0x50)
> +#define QSERDES_COM_LOCK_CMP3_MODE0		COM_OFF(0x54)
> +#define QSERDES_COM_LOCK_CMP1_MODE1		COM_OFF(0x58)
> +#define QSERDES_COM_LOCK_CMP2_MODE1		COM_OFF(0x5C)
> +#define QSERDES_COM_LOCK_CMP3_MODE1		COM_OFF(0x60)
> +#define QSERDES_COM_CP_CTRL_MODE0		COM_OFF(0x78)
> +#define QSERDES_COM_CP_CTRL_MODE1		COM_OFF(0x7C)
> +#define QSERDES_COM_PLL_RCTRL_MODE0		COM_OFF(0x84)
> +#define QSERDES_COM_PLL_RCTRL_MODE1		COM_OFF(0x88)
> +#define QSERDES_COM_PLL_CCTRL_MODE0		COM_OFF(0x90)
> +#define QSERDES_COM_PLL_CCTRL_MODE1		COM_OFF(0x94)
> +#define QSERDES_COM_SYSCLK_EN_SEL		COM_OFF(0xAC)
> +#define QSERDES_COM_RESETSM_CNTRL		COM_OFF(0xB4)
> +#define QSERDES_COM_LOCK_CMP_EN			COM_OFF(0xC8)
> +#define QSERDES_COM_LOCK_CMP_CFG		COM_OFF(0xCC)
> +#define QSERDES_COM_DEC_START_MODE0		COM_OFF(0xD0)
> +#define QSERDES_COM_DEC_START_MODE1		COM_OFF(0xD4)
> +#define QSERDES_COM_DIV_FRAC_START1_MODE0	COM_OFF(0xDC)
> +#define QSERDES_COM_DIV_FRAC_START2_MODE0	COM_OFF(0xE0)
> +#define QSERDES_COM_DIV_FRAC_START3_MODE0	COM_OFF(0xE4)
> +#define QSERDES_COM_DIV_FRAC_START1_MODE1	COM_OFF(0xE8)
> +#define QSERDES_COM_DIV_FRAC_START2_MODE1	COM_OFF(0xEC)
> +#define QSERDES_COM_DIV_FRAC_START3_MODE1	COM_OFF(0xF0)
> +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0	COM_OFF(0x108)
> +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0	COM_OFF(0x10C)
> +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1	COM_OFF(0x110)
> +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1	COM_OFF(0x114)
> +#define QSERDES_COM_VCO_TUNE_CTRL		COM_OFF(0x124)
> +#define QSERDES_COM_VCO_TUNE_MAP		COM_OFF(0x128)
> +#define QSERDES_COM_VCO_TUNE1_MODE0		COM_OFF(0x12C)
> +#define QSERDES_COM_VCO_TUNE2_MODE0		COM_OFF(0x130)
> +#define QSERDES_COM_VCO_TUNE1_MODE1		COM_OFF(0x134)
> +#define QSERDES_COM_VCO_TUNE2_MODE1		COM_OFF(0x138)
> +#define QSERDES_COM_VCO_TUNE_TIMER1		COM_OFF(0x144)
> +#define QSERDES_COM_VCO_TUNE_TIMER2		COM_OFF(0x148)
> +#define QSERDES_COM_CLK_SELECT			COM_OFF(0x174)
> +#define QSERDES_COM_HSCLK_SEL			COM_OFF(0x178)
> +#define QSERDES_COM_CORECLK_DIV			COM_OFF(0x184)
> +#define QSERDES_COM_CORE_CLK_EN			COM_OFF(0x18C)
> +#define QSERDES_COM_CMN_CONFIG			COM_OFF(0x194)
> +#define QSERDES_COM_SVS_MODE_CLK_SEL		COM_OFF(0x19C)
> +#define QSERDES_COM_CORECLK_DIV_MODE1		COM_OFF(0x1BC)
> +
> +/* UFS PHY registers */
> +#define UFS_PHY_PHY_START			PHY_OFF(0x00)
> +#define UFS_PHY_POWER_DOWN_CONTROL		PHY_OFF(0x04)
> +#define UFS_PHY_PCS_READY_STATUS		PHY_OFF(0x168)
> +
> +/* UFS PHY TX registers */
> +#define QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN	TX_OFF(0, 0x68)
> +#define QSERDES_TX_LANE_MODE				TX_OFF(0, 0x94)
> +
> +/* UFS PHY RX registers */
> +#define QSERDES_RX_UCDR_FASTLOCK_FO_GAIN	RX_OFF(0, 0x40)
> +#define QSERDES_RX_RX_TERM_BW			RX_OFF(0, 0x90)
> +#define QSERDES_RX_RX_EQ_GAIN1_LSB		RX_OFF(0, 0xC4)
> +#define QSERDES_RX_RX_EQ_GAIN1_MSB		RX_OFF(0, 0xC8)
> +#define QSERDES_RX_RX_EQ_GAIN2_LSB		RX_OFF(0, 0xCC)
> +#define QSERDES_RX_RX_EQ_GAIN2_MSB		RX_OFF(0, 0xD0)
> +#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2	RX_OFF(0, 0xD8)
> +#define QSERDES_RX_SIGDET_CNTRL			RX_OFF(0, 0x114)
> +#define QSERDES_RX_SIGDET_LVL			RX_OFF(0, 0x118)
> +#define QSERDES_RX_SIGDET_DEGLITCH_CNTRL	RX_OFF(0, 0x11C)
> +#define QSERDES_RX_RX_INTERFACE_MODE		RX_OFF(0, 0x12C)
> +
> +/*
> + * This structure represents the 14nm specific phy.
> + * common_cfg MUST remain the first field in this structure
> + * in case extra fields are added. This way, when calling
> + * get_ufs_qcom_phy() of generic phy, we can extract the
> + * common phy structure (struct ufs_qcom_phy) out of it
> + * regardless of the relevant specific phy.
> + */
> +struct ufs_qcom_phy_qmp_14nm {
> +	struct ufs_qcom_phy common_cfg;
> +};
> +
> +static struct ufs_qcom_phy_calibration phy_cal_table_rate_A[] = {
> +	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CMN_CONFIG, 0x0e),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL, 0xd7),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CLK_SELECT, 0x30),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYS_CLK_CTRL, 0x06),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BG_TIMER, 0x0a),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_HSCLK_SEL, 0x05),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV, 0x0a),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV_MODE1, 0x0a),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_EN, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_CTRL, 0x10),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x20),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORE_CLK_EN, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_CFG, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_TIMER1, 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_TIMER2, 0x3f),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x14),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SVS_MODE_CLK_SEL, 0x05),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE0, 0x82),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE0, 0x0b),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE0, 0x28),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE0, 0x28),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE0, 0x02),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE0, 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE0, 0x0c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP3_MODE0, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE1, 0x98),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1_MODE1, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2_MODE1, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3_MODE1, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE1, 0x0b),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE1, 0x16),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE1, 0x28),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE1, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE1, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE1, 0xd6),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE1, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE1, 0x32),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE1, 0x0f),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP3_MODE1, 0x00),
> +
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN,
> 0x45),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE, 0x02),
> +
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_LVL, 0x24),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_CNTRL, 0x02),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_INTERFACE_MODE, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x18),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_UCDR_FASTLOCK_FO_GAIN, 0x0B),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_TERM_BW, 0x5B),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB, 0xFF),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB, 0x3F),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB, 0xFF),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB, 0x0F),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0E),
> +};
> +
> +static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x54),
> +};
> +
> +#endif
> --
> 1.8.5.2
>
> --
> QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
> of Code Aurora Forum, hosted by The Linux Foundation
> --
> To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v7 4/5] phy: qcom-ufs: add support for 14nm phy
@ 2015-01-15 15:18     ` dovl
  0 siblings, 0 replies; 27+ messages in thread
From: dovl @ 2015-01-15 15:18 UTC (permalink / raw)
  To: Yaniv Gardi
  Cc: james.bottomley, hch, linux-kernel, linux-scsi, linux-arm-msm,
	santoshsy, linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Kishon Vijay Abraham I, Grant Likely, Rob Herring,
	open list:OPEN FIRMWARE AND...

Reviewed-by: Dov Levenglick <dovl@codeaurora.org>

> This change adds a support for a 14nm qcom-ufs phy that is
> required in platforms that use ufs-qcom controller.
>
> Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>
>
> ---
>  drivers/phy/Makefile                |   1 +
>  drivers/phy/phy-qcom-ufs-qmp-14nm.c | 201
> ++++++++++++++++++++++++++++++++++++
>  drivers/phy/phy-qcom-ufs-qmp-14nm.h | 177 +++++++++++++++++++++++++++++++
>  3 files changed, 379 insertions(+)
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-14nm.c
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-14nm.h
>
> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
> index 781b2fa..cfbb720 100644
> --- a/drivers/phy/Makefile
> +++ b/drivers/phy/Makefile
> @@ -36,3 +36,4 @@ obj-$(CONFIG_PHY_STIH407_USB)		+=
> phy-stih407-usb.o
>  obj-$(CONFIG_PHY_STIH41X_USB)		+= phy-stih41x-usb.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-ufs-qmp-14nm.c
> b/drivers/phy/phy-qcom-ufs-qmp-14nm.c
> new file mode 100644
> index 0000000..f5fc50a
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs-qmp-14nm.c
> @@ -0,0 +1,201 @@
> +/*
> + * Copyright (c) 2013-2015, 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 "phy-qcom-ufs-qmp-14nm.h"
> +
> +#define UFS_PHY_NAME "ufs_phy_qmp_14nm"
> +#define UFS_PHY_VDDA_PHY_UV	(925000)
> +
> +static
> +int ufs_qcom_phy_qmp_14nm_phy_calibrate(struct ufs_qcom_phy
> *ufs_qcom_phy,
> +					bool is_rate_B)
> +{
> +	int tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A);
> +	int tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
> +	int err;
> +
> +	err = ufs_qcom_phy_calibrate(ufs_qcom_phy, phy_cal_table_rate_A,
> +		tbl_size_A, phy_cal_table_rate_B, tbl_size_B, is_rate_B);
> +
> +	if (err)
> +		dev_err(ufs_qcom_phy->dev,
> +			"%s: ufs_qcom_phy_calibrate() failed %d\n",
> +			__func__, err);
> +	return err;
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_14nm_advertise_quirks(struct ufs_qcom_phy
> *phy_common)
> +{
> +	phy_common->quirks =
> +		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
> +}
> +
> +static int ufs_qcom_phy_qmp_14nm_init(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy_qmp_14nm *phy = phy_get_drvdata(generic_phy);
> +	struct ufs_qcom_phy *phy_common = &phy->common_cfg;
> +	int err;
> +
> +	err = ufs_qcom_phy_init_clks(generic_phy, phy_common);
> +	if (err) {
> +		dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_clks()
> failed %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	err = ufs_qcom_phy_init_vregulators(generic_phy, phy_common);
> +	if (err) {
> +		dev_err(phy_common->dev, "%s:
> ufs_qcom_phy_init_vregulators() failed %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +	phy_common->vdda_phy.max_uV = UFS_PHY_VDDA_PHY_UV;
> +	phy_common->vdda_phy.min_uV = UFS_PHY_VDDA_PHY_UV;
> +
> +	ufs_qcom_phy_qmp_14nm_advertise_quirks(phy_common);
> +
> +out:
> +	return err;
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_14nm_power_control(struct ufs_qcom_phy *phy, bool
> val)
> +{
> +	writel_relaxed(val ? 0x1 : 0x0, phy->mmio +
> UFS_PHY_POWER_DOWN_CONTROL);
> +	/*
> +	 * Before any transactions involving PHY, ensure PHY knows
> +	 * that it's analog rail is powered ON (or OFF).
> +	 */
> +	mb();
> +}
> +
> +static inline
> +void ufs_qcom_phy_qmp_14nm_set_tx_lane_enable(struct ufs_qcom_phy *phy,
> u32 val)
> +{
> +	/*
> +	 * 14nm PHY does not have TX_LANE_ENABLE register.
> +	 * Implement this function so as not to propagate error to caller.
> +	 */
> +}
> +
> +static inline void ufs_qcom_phy_qmp_14nm_start_serdes(struct ufs_qcom_phy
> *phy)
> +{
> +	u32 tmp;
> +
> +	tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
> +	tmp &= ~MASK_SERDES_START;
> +	tmp |= (1 << OFFSET_SERDES_START);
> +	writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
> +	/* Ensure register value is committed */
> +	mb();
> +}
> +
> +static int ufs_qcom_phy_qmp_14nm_is_pcs_ready(struct ufs_qcom_phy
> *phy_common)
> +{
> +	int err = 0;
> +	u32 val;
> +
> +	err = readl_poll_timeout(phy_common->mmio +
> UFS_PHY_PCS_READY_STATUS,
> +		val, (val & MASK_PCS_READY), 10, 1000000);
> +	if (err)
> +		dev_err(phy_common->dev, "%s: poll for pcs failed err =
> %d\n",
> +			__func__, err);
> +	return err;
> +}
> +
> +static struct phy_ops ufs_qcom_phy_qmp_14nm_phy_ops = {
> +	.init		= ufs_qcom_phy_qmp_14nm_init,
> +	.exit		= ufs_qcom_phy_exit,
> +	.power_on	= ufs_qcom_phy_power_on,
> +	.power_off	= ufs_qcom_phy_power_off,
> +	.owner		= THIS_MODULE,
> +};
> +
> +static struct ufs_qcom_phy_specific_ops phy_14nm_ops = {
> +	.calibrate_phy		= ufs_qcom_phy_qmp_14nm_phy_calibrate,
> +	.start_serdes		= ufs_qcom_phy_qmp_14nm_start_serdes,
> +	.is_physical_coding_sublayer_ready =
> ufs_qcom_phy_qmp_14nm_is_pcs_ready,
> +	.set_tx_lane_enable	=
> ufs_qcom_phy_qmp_14nm_set_tx_lane_enable,
> +	.power_control		= ufs_qcom_phy_qmp_14nm_power_control,
> +};
> +
> +static int ufs_qcom_phy_qmp_14nm_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct phy *generic_phy;
> +	struct ufs_qcom_phy_qmp_14nm *phy;
> +	int err = 0;
> +
> +	phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
> +	if (!phy) {
> +		dev_err(dev, "%s: failed to allocate phy\n", __func__);
> +		err = -ENOMEM;
> +		goto out;
> +	}
> +
> +	generic_phy = ufs_qcom_phy_generic_probe(pdev, &phy->common_cfg,
> +				&ufs_qcom_phy_qmp_14nm_phy_ops,
> &phy_14nm_ops);
> +
> +	if (!generic_phy) {
> +		dev_err(dev, "%s: ufs_qcom_phy_generic_probe() failed\n",
> +			__func__);
> +		err = -EIO;
> +		goto out;
> +	}
> +
> +	phy_set_drvdata(generic_phy, phy);
> +
> +	strlcpy(phy->common_cfg.name, UFS_PHY_NAME,
> +		sizeof(phy->common_cfg.name));
> +
> +out:
> +	return err;
> +}
> +
> +static int ufs_qcom_phy_qmp_14nm_remove(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct phy *generic_phy = to_phy(dev);
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +	int err = 0;
> +
> +	err = ufs_qcom_phy_remove(generic_phy, ufs_qcom_phy);
> +	if (err)
> +		dev_err(dev, "%s: ufs_qcom_phy_remove failed = %d\n",
> +			__func__, err);
> +
> +	return err;
> +}
> +
> +static const struct of_device_id ufs_qcom_phy_qmp_14nm_of_match[] = {
> +	{.compatible = "qcom,ufs-phy-qmp-14nm"},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_14nm_of_match);
> +
> +static struct platform_driver ufs_qcom_phy_qmp_14nm_driver = {
> +	.probe = ufs_qcom_phy_qmp_14nm_probe,
> +	.remove = ufs_qcom_phy_qmp_14nm_remove,
> +	.driver = {
> +		.of_match_table = ufs_qcom_phy_qmp_14nm_of_match,
> +		.name = "ufs_qcom_phy_qmp_14nm",
> +		.owner = THIS_MODULE,
> +	},
> +};
> +
> +module_platform_driver(ufs_qcom_phy_qmp_14nm_driver);
> +
> +MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 14nm");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/phy/phy-qcom-ufs-qmp-14nm.h
> b/drivers/phy/phy-qcom-ufs-qmp-14nm.h
> new file mode 100644
> index 0000000..3aefdba
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs-qmp-14nm.h
> @@ -0,0 +1,177 @@
> +/*
> + * Copyright (c) 2013-2015, 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.
> + *
> + */
> +
> +#ifndef UFS_QCOM_PHY_QMP_14NM_H_
> +#define UFS_QCOM_PHY_QMP_14NM_H_
> +
> +#include "phy-qcom-ufs-i.h"
> +
> +/* QCOM UFS PHY control registers */
> +#define COM_OFF(x)	(0x000 + x)
> +#define PHY_OFF(x)	(0xC00 + x)
> +#define TX_OFF(n, x)	(0x400 + (0x400 * n) + x)
> +#define RX_OFF(n, x)	(0x600 + (0x400 * n) + x)
> +
> +/* UFS PHY QSERDES COM registers */
> +#define QSERDES_COM_BG_TIMER			COM_OFF(0x0C)
> +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN		COM_OFF(0x34)
> +#define QSERDES_COM_SYS_CLK_CTRL		COM_OFF(0x3C)
> +#define QSERDES_COM_LOCK_CMP1_MODE0		COM_OFF(0x4C)
> +#define QSERDES_COM_LOCK_CMP2_MODE0		COM_OFF(0x50)
> +#define QSERDES_COM_LOCK_CMP3_MODE0		COM_OFF(0x54)
> +#define QSERDES_COM_LOCK_CMP1_MODE1		COM_OFF(0x58)
> +#define QSERDES_COM_LOCK_CMP2_MODE1		COM_OFF(0x5C)
> +#define QSERDES_COM_LOCK_CMP3_MODE1		COM_OFF(0x60)
> +#define QSERDES_COM_CP_CTRL_MODE0		COM_OFF(0x78)
> +#define QSERDES_COM_CP_CTRL_MODE1		COM_OFF(0x7C)
> +#define QSERDES_COM_PLL_RCTRL_MODE0		COM_OFF(0x84)
> +#define QSERDES_COM_PLL_RCTRL_MODE1		COM_OFF(0x88)
> +#define QSERDES_COM_PLL_CCTRL_MODE0		COM_OFF(0x90)
> +#define QSERDES_COM_PLL_CCTRL_MODE1		COM_OFF(0x94)
> +#define QSERDES_COM_SYSCLK_EN_SEL		COM_OFF(0xAC)
> +#define QSERDES_COM_RESETSM_CNTRL		COM_OFF(0xB4)
> +#define QSERDES_COM_LOCK_CMP_EN			COM_OFF(0xC8)
> +#define QSERDES_COM_LOCK_CMP_CFG		COM_OFF(0xCC)
> +#define QSERDES_COM_DEC_START_MODE0		COM_OFF(0xD0)
> +#define QSERDES_COM_DEC_START_MODE1		COM_OFF(0xD4)
> +#define QSERDES_COM_DIV_FRAC_START1_MODE0	COM_OFF(0xDC)
> +#define QSERDES_COM_DIV_FRAC_START2_MODE0	COM_OFF(0xE0)
> +#define QSERDES_COM_DIV_FRAC_START3_MODE0	COM_OFF(0xE4)
> +#define QSERDES_COM_DIV_FRAC_START1_MODE1	COM_OFF(0xE8)
> +#define QSERDES_COM_DIV_FRAC_START2_MODE1	COM_OFF(0xEC)
> +#define QSERDES_COM_DIV_FRAC_START3_MODE1	COM_OFF(0xF0)
> +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0	COM_OFF(0x108)
> +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0	COM_OFF(0x10C)
> +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1	COM_OFF(0x110)
> +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1	COM_OFF(0x114)
> +#define QSERDES_COM_VCO_TUNE_CTRL		COM_OFF(0x124)
> +#define QSERDES_COM_VCO_TUNE_MAP		COM_OFF(0x128)
> +#define QSERDES_COM_VCO_TUNE1_MODE0		COM_OFF(0x12C)
> +#define QSERDES_COM_VCO_TUNE2_MODE0		COM_OFF(0x130)
> +#define QSERDES_COM_VCO_TUNE1_MODE1		COM_OFF(0x134)
> +#define QSERDES_COM_VCO_TUNE2_MODE1		COM_OFF(0x138)
> +#define QSERDES_COM_VCO_TUNE_TIMER1		COM_OFF(0x144)
> +#define QSERDES_COM_VCO_TUNE_TIMER2		COM_OFF(0x148)
> +#define QSERDES_COM_CLK_SELECT			COM_OFF(0x174)
> +#define QSERDES_COM_HSCLK_SEL			COM_OFF(0x178)
> +#define QSERDES_COM_CORECLK_DIV			COM_OFF(0x184)
> +#define QSERDES_COM_CORE_CLK_EN			COM_OFF(0x18C)
> +#define QSERDES_COM_CMN_CONFIG			COM_OFF(0x194)
> +#define QSERDES_COM_SVS_MODE_CLK_SEL		COM_OFF(0x19C)
> +#define QSERDES_COM_CORECLK_DIV_MODE1		COM_OFF(0x1BC)
> +
> +/* UFS PHY registers */
> +#define UFS_PHY_PHY_START			PHY_OFF(0x00)
> +#define UFS_PHY_POWER_DOWN_CONTROL		PHY_OFF(0x04)
> +#define UFS_PHY_PCS_READY_STATUS		PHY_OFF(0x168)
> +
> +/* UFS PHY TX registers */
> +#define QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN	TX_OFF(0, 0x68)
> +#define QSERDES_TX_LANE_MODE				TX_OFF(0, 0x94)
> +
> +/* UFS PHY RX registers */
> +#define QSERDES_RX_UCDR_FASTLOCK_FO_GAIN	RX_OFF(0, 0x40)
> +#define QSERDES_RX_RX_TERM_BW			RX_OFF(0, 0x90)
> +#define QSERDES_RX_RX_EQ_GAIN1_LSB		RX_OFF(0, 0xC4)
> +#define QSERDES_RX_RX_EQ_GAIN1_MSB		RX_OFF(0, 0xC8)
> +#define QSERDES_RX_RX_EQ_GAIN2_LSB		RX_OFF(0, 0xCC)
> +#define QSERDES_RX_RX_EQ_GAIN2_MSB		RX_OFF(0, 0xD0)
> +#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2	RX_OFF(0, 0xD8)
> +#define QSERDES_RX_SIGDET_CNTRL			RX_OFF(0, 0x114)
> +#define QSERDES_RX_SIGDET_LVL			RX_OFF(0, 0x118)
> +#define QSERDES_RX_SIGDET_DEGLITCH_CNTRL	RX_OFF(0, 0x11C)
> +#define QSERDES_RX_RX_INTERFACE_MODE		RX_OFF(0, 0x12C)
> +
> +/*
> + * This structure represents the 14nm specific phy.
> + * common_cfg MUST remain the first field in this structure
> + * in case extra fields are added. This way, when calling
> + * get_ufs_qcom_phy() of generic phy, we can extract the
> + * common phy structure (struct ufs_qcom_phy) out of it
> + * regardless of the relevant specific phy.
> + */
> +struct ufs_qcom_phy_qmp_14nm {
> +	struct ufs_qcom_phy common_cfg;
> +};
> +
> +static struct ufs_qcom_phy_calibration phy_cal_table_rate_A[] = {
> +	UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CMN_CONFIG, 0x0e),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL, 0xd7),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CLK_SELECT, 0x30),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYS_CLK_CTRL, 0x06),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BG_TIMER, 0x0a),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_HSCLK_SEL, 0x05),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV, 0x0a),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV_MODE1, 0x0a),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_EN, 0x01),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_CTRL, 0x10),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x20),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORE_CLK_EN, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_CFG, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_TIMER1, 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_TIMER2, 0x3f),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x14),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SVS_MODE_CLK_SEL, 0x05),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE0, 0x82),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE0, 0x0b),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE0, 0x28),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE0, 0x28),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE0, 0x02),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE0, 0xff),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE0, 0x0c),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP3_MODE0, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE1, 0x98),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1_MODE1, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2_MODE1, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3_MODE1, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE1, 0x0b),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE1, 0x16),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE1, 0x28),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE1, 0x80),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE1, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE1, 0xd6),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE1, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE1, 0x32),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE1, 0x0f),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP3_MODE1, 0x00),
> +
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN,
> 0x45),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE, 0x02),
> +
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_LVL, 0x24),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_CNTRL, 0x02),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_INTERFACE_MODE, 0x00),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x18),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_UCDR_FASTLOCK_FO_GAIN, 0x0B),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_TERM_BW, 0x5B),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB, 0xFF),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB, 0x3F),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB, 0xFF),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB, 0x0F),
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0E),
> +};
> +
> +static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
> +	UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x54),
> +};
> +
> +#endif
> --
> 1.8.5.2
>
> --
> QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
> of Code Aurora Forum, hosted by The Linux Foundation
> --
> To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>



^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v7 3/5] scsi: ufs-qcom: add support for Qualcomm Technologies Inc platforms
  2015-01-15 14:32 ` [PATCH v7 3/5] scsi: ufs-qcom: add support for Qualcomm Technologies Inc platforms Yaniv Gardi
@ 2015-01-15 15:20     ` Dov Levenglick
  0 siblings, 0 replies; 27+ messages in thread
From: Dov Levenglick @ 2015-01-15 15:20 UTC (permalink / raw)
  Cc: james.bottomley, hch, linux-kernel, linux-scsi, linux-arm-msm,
	santoshsy, linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Vinayak Holikatti, James E.J. Bottomley

Reviewed-by: Dov Levenglick <dovl@codeaurora.org>

> This change adds support for Qualcomm Technologies Inc platforms that
> use UFS driver. for example, it adds :
> - PM specific operations during hibern8, suspend, resume, clock setup
> - qcom-ufs generic phy driver initialization, calibration,
>   power-on/off sequence, etc.
> - UFS Controller specific configuration
> - Rate, Gear, Mode negotiation between device and controller
>
> Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>
>
> ---
>  drivers/scsi/ufs/Kconfig    |   13 +
>  drivers/scsi/ufs/Makefile   |    1 +
>  drivers/scsi/ufs/ufs-qcom.c | 1004
> +++++++++++++++++++++++++++++++++++++++++++
>  drivers/scsi/ufs/ufs-qcom.h |  170 ++++++++
>  4 files changed, 1188 insertions(+)
>  create mode 100644 drivers/scsi/ufs/ufs-qcom.c
>  create mode 100644 drivers/scsi/ufs/ufs-qcom.h
>
> diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig
> index 6e07b2a..8a1f4b3 100644
> --- a/drivers/scsi/ufs/Kconfig
> +++ b/drivers/scsi/ufs/Kconfig
> @@ -70,3 +70,16 @@ config SCSI_UFSHCD_PLATFORM
>  	If you have a controller with this interface, say Y or M here.
>
>  	  If unsure, say N.
> +
> +config SCSI_UFS_QCOM
> +	bool "QCOM specific hooks to UFS controller platform driver"
> +	depends on SCSI_UFSHCD_PLATFORM && ARCH_MSM
> +	select PHY_QCOM_UFS
> +	help
> +	  This selects the QCOM specific additions to UFSHCD platform
> driver.
> +	  UFS host on QCOM needs some vendor specific configuration before
> +	  accessing the hardware which includes PHY configuration and
> vendor
> +	  specific registers.
> +
> +	  Select this if you have UFS controller on QCOM chipset.
> +	  If unsure, say N.
> diff --git a/drivers/scsi/ufs/Makefile b/drivers/scsi/ufs/Makefile
> index 1e5bd48..8303bcc 100644
> --- a/drivers/scsi/ufs/Makefile
> +++ b/drivers/scsi/ufs/Makefile
> @@ -1,4 +1,5 @@
>  # UFSHCD makefile
> +obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-qcom.o
>  obj-$(CONFIG_SCSI_UFSHCD) += ufshcd.o
>  obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o
>  obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o
> diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c
> new file mode 100644
> index 0000000..9217af9
> --- /dev/null
> +++ b/drivers/scsi/ufs/ufs-qcom.c
> @@ -0,0 +1,1004 @@
> +/*
> + * Copyright (c) 2013-2015, 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/time.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/phy/phy.h>
> +
> +#include <linux/phy/phy-qcom-ufs.h>
> +#include "ufshcd.h"
> +#include "unipro.h"
> +#include "ufs-qcom.h"
> +#include "ufshci.h"
> +
> +static struct ufs_qcom_host *ufs_qcom_hosts[MAX_UFS_QCOM_HOSTS];
> +
> +static void ufs_qcom_get_speed_mode(struct ufs_pa_layer_attr *p, char
> *result);
> +static int ufs_qcom_get_bus_vote(struct ufs_qcom_host *host,
> +		const char *speed_mode);
> +static int ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote);
> +
> +static int ufs_qcom_get_connected_tx_lanes(struct ufs_hba *hba, u32
> *tx_lanes)
> +{
> +	int err = 0;
> +
> +	err = ufshcd_dme_get(hba,
> +			UIC_ARG_MIB(PA_CONNECTEDTXDATALANES), tx_lanes);
> +	if (err)
> +		dev_err(hba->dev, "%s: couldn't read
> PA_CONNECTEDTXDATALANES %d\n",
> +				__func__, err);
> +
> +	return err;
> +}
> +
> +static int ufs_qcom_host_clk_get(struct device *dev,
> +		const char *name, struct clk **clk_out)
> +{
> +	struct clk *clk;
> +	int err = 0;
> +
> +	clk = devm_clk_get(dev, name);
> +	if (IS_ERR(clk)) {
> +		err = PTR_ERR(clk);
> +		dev_err(dev, "%s: failed to get %s err %d",
> +				__func__, name, err);
> +	} else {
> +		*clk_out = clk;
> +	}
> +
> +	return err;
> +}
> +
> +static int ufs_qcom_host_clk_enable(struct device *dev,
> +		const char *name, struct clk *clk)
> +{
> +	int err = 0;
> +
> +	err = clk_prepare_enable(clk);
> +	if (err)
> +		dev_err(dev, "%s: %s enable failed %d\n", __func__, name,
> err);
> +
> +	return err;
> +}
> +
> +static void ufs_qcom_disable_lane_clks(struct ufs_qcom_host *host)
> +{
> +	if (!host->is_lane_clks_enabled)
> +		return;
> +
> +	clk_disable_unprepare(host->tx_l1_sync_clk);
> +	clk_disable_unprepare(host->tx_l0_sync_clk);
> +	clk_disable_unprepare(host->rx_l1_sync_clk);
> +	clk_disable_unprepare(host->rx_l0_sync_clk);
> +
> +	host->is_lane_clks_enabled = false;
> +}
> +
> +static int ufs_qcom_enable_lane_clks(struct ufs_qcom_host *host)
> +{
> +	int err = 0;
> +	struct device *dev = host->hba->dev;
> +
> +	if (host->is_lane_clks_enabled)
> +		return 0;
> +
> +	err = ufs_qcom_host_clk_enable(dev, "rx_lane0_sync_clk",
> +		host->rx_l0_sync_clk);
> +	if (err)
> +		goto out;
> +
> +	err = ufs_qcom_host_clk_enable(dev, "tx_lane0_sync_clk",
> +		host->tx_l0_sync_clk);
> +	if (err)
> +		goto disable_rx_l0;
> +
> +	err = ufs_qcom_host_clk_enable(dev, "rx_lane1_sync_clk",
> +		host->rx_l1_sync_clk);
> +	if (err)
> +		goto disable_tx_l0;
> +
> +	err = ufs_qcom_host_clk_enable(dev, "tx_lane1_sync_clk",
> +		host->tx_l1_sync_clk);
> +	if (err)
> +		goto disable_rx_l1;
> +
> +	host->is_lane_clks_enabled = true;
> +	goto out;
> +
> +disable_rx_l1:
> +	clk_disable_unprepare(host->rx_l1_sync_clk);
> +disable_tx_l0:
> +	clk_disable_unprepare(host->tx_l0_sync_clk);
> +disable_rx_l0:
> +	clk_disable_unprepare(host->rx_l0_sync_clk);
> +out:
> +	return err;
> +}
> +
> +static int ufs_qcom_init_lane_clks(struct ufs_qcom_host *host)
> +{
> +	int err = 0;
> +	struct device *dev = host->hba->dev;
> +
> +	err = ufs_qcom_host_clk_get(dev,
> +			"rx_lane0_sync_clk", &host->rx_l0_sync_clk);
> +	if (err)
> +		goto out;
> +
> +	err = ufs_qcom_host_clk_get(dev,
> +			"tx_lane0_sync_clk", &host->tx_l0_sync_clk);
> +	if (err)
> +		goto out;
> +
> +	err = ufs_qcom_host_clk_get(dev, "rx_lane1_sync_clk",
> +		&host->rx_l1_sync_clk);
> +	if (err)
> +		goto out;
> +
> +	err = ufs_qcom_host_clk_get(dev, "tx_lane1_sync_clk",
> +		&host->tx_l1_sync_clk);
> +out:
> +	return err;
> +}
> +
> +static int ufs_qcom_link_startup_post_change(struct ufs_hba *hba)
> +{
> +	struct ufs_qcom_host *host = hba->priv;
> +	struct phy *phy = host->generic_phy;
> +	u32 tx_lanes;
> +	int err = 0;
> +
> +	err = ufs_qcom_get_connected_tx_lanes(hba, &tx_lanes);
> +	if (err)
> +		goto out;
> +
> +	err = ufs_qcom_phy_set_tx_lane_enable(phy, tx_lanes);
> +	if (err)
> +		dev_err(hba->dev, "%s: ufs_qcom_phy_set_tx_lane_enable
> failed\n",
> +			__func__);
> +
> +out:
> +	return err;
> +}
> +
> +static int ufs_qcom_check_hibern8(struct ufs_hba *hba)
> +{
> +	int err;
> +	u32 tx_fsm_val = 0;
> +	unsigned long timeout = jiffies +
> msecs_to_jiffies(HBRN8_POLL_TOUT_MS);
> +
> +	do {
> +		err = ufshcd_dme_get(hba,
> +			UIC_ARG_MIB(MPHY_TX_FSM_STATE), &tx_fsm_val);
> +		if (err || tx_fsm_val == TX_FSM_HIBERN8)
> +			break;
> +
> +		/* sleep for max. 200us */
> +		usleep_range(100, 200);
> +	} while (time_before(jiffies, timeout));
> +
> +	/*
> +	 * we might have scheduled out for long during polling so
> +	 * check the state again.
> +	 */
> +	if (time_after(jiffies, timeout))
> +		err = ufshcd_dme_get(hba,
> +				UIC_ARG_MIB(MPHY_TX_FSM_STATE),
> &tx_fsm_val);
> +
> +	if (err) {
> +		dev_err(hba->dev, "%s: unable to get TX_FSM_STATE, err
> %d\n",
> +				__func__, err);
> +	} else if (tx_fsm_val != TX_FSM_HIBERN8) {
> +		err = tx_fsm_val;
> +		dev_err(hba->dev, "%s: invalid TX_FSM_STATE = %d\n",
> +				__func__, err);
> +	}
> +
> +	return err;
> +}
> +
> +static int ufs_qcom_power_up_sequence(struct ufs_hba *hba)
> +{
> +	struct ufs_qcom_host *host = hba->priv;
> +	struct phy *phy = host->generic_phy;
> +	int ret = 0;
> +	u8 major;
> +	u16 minor, step;
> +	bool is_rate_B = (UFS_QCOM_LIMIT_HS_RATE == PA_HS_MODE_B)
> +							? true : false;
> +
> +	/* Assert PHY reset and apply PHY calibration values */
> +	ufs_qcom_assert_reset(hba);
> +	/* provide 1ms delay to let the reset pulse propagate */
> +	usleep_range(1000, 1100);
> +
> +	ufs_qcom_get_controller_revision(hba, &major, &minor, &step);
> +	ufs_qcom_phy_save_controller_version(phy, major, minor, step);
> +	ret = ufs_qcom_phy_calibrate_phy(phy, is_rate_B);
> +	if (ret) {
> +		dev_err(hba->dev, "%s: ufs_qcom_phy_calibrate_phy()
> failed, ret = %d\n",
> +			__func__, ret);
> +		goto out;
> +	}
> +
> +	/* De-assert PHY reset and start serdes */
> +	ufs_qcom_deassert_reset(hba);
> +
> +	/*
> +	 * after reset deassertion, phy will need all ref clocks,
> +	 * voltage, current to settle down before starting serdes.
> +	 */
> +	usleep_range(1000, 1100);
> +	ret = ufs_qcom_phy_start_serdes(phy);
> +	if (ret) {
> +		dev_err(hba->dev, "%s: ufs_qcom_phy_start_serdes() failed,
> ret = %d\n",
> +			__func__, ret);
> +		goto out;
> +	}
> +
> +	ret = ufs_qcom_phy_is_pcs_ready(phy);
> +	if (ret)
> +		dev_err(hba->dev, "%s: is_physical_coding_sublayer_ready()
> failed, ret = %d\n",
> +			__func__, ret);
> +
> +out:
> +	return ret;
> +}
> +
> +/*
> + * The UTP controller has a number of internal clock gating cells (CGCs).
> + * Internal hardware sub-modules within the UTP controller control the
> CGCs.
> + * Hardware CGCs disable the clock to inactivate UTP sub-modules not
> involved
> + * in a specific operation, UTP controller CGCs are by default disabled
> and
> + * this function enables them (after every UFS link startup) to save some
> power
> + * leakage.
> + */
> +static void ufs_qcom_enable_hw_clk_gating(struct ufs_hba *hba)
> +{
> +	ufshcd_writel(hba,
> +		ufshcd_readl(hba, REG_UFS_CFG2) | REG_UFS_CFG2_CGC_EN_ALL,
> +		REG_UFS_CFG2);
> +
> +	/* Ensure that HW clock gating is enabled before next operations
> */
> +	mb();
> +}
> +
> +static int ufs_qcom_hce_enable_notify(struct ufs_hba *hba, bool status)
> +{
> +	struct ufs_qcom_host *host = hba->priv;
> +	int err = 0;
> +
> +	switch (status) {
> +	case PRE_CHANGE:
> +		ufs_qcom_power_up_sequence(hba);
> +		/*
> +		 * The PHY PLL output is the source of tx/rx lane symbol
> +		 * clocks, hence, enable the lane clocks only after PHY
> +		 * is initialized.
> +		 */
> +		err = ufs_qcom_enable_lane_clks(host);
> +		break;
> +	case POST_CHANGE:
> +		/* check if UFS PHY moved from DISABLED to HIBERN8 */
> +		err = ufs_qcom_check_hibern8(hba);
> +		ufs_qcom_enable_hw_clk_gating(hba);
> +
> +		break;
> +	default:
> +		dev_err(hba->dev, "%s: invalid status %d\n", __func__,
> status);
> +		err = -EINVAL;
> +		break;
> +	}
> +	return err;
> +}
> +
> +/**
> + * Returns non-zero for success (which rate of core_clk) and 0
> + * in case of a failure
> + */
> +static unsigned long
> +ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear, u32 hs, u32 rate)
> +{
> +	struct ufs_clk_info *clki;
> +	u32 core_clk_period_in_ns;
> +	u32 tx_clk_cycles_per_us = 0;
> +	unsigned long core_clk_rate = 0;
> +	u32 core_clk_cycles_per_us = 0;
> +
> +	static u32 pwm_fr_table[][2] = {
> +		{UFS_PWM_G1, 0x1},
> +		{UFS_PWM_G2, 0x1},
> +		{UFS_PWM_G3, 0x1},
> +		{UFS_PWM_G4, 0x1},
> +	};
> +
> +	static u32 hs_fr_table_rA[][2] = {
> +		{UFS_HS_G1, 0x1F},
> +		{UFS_HS_G2, 0x3e},
> +	};
> +
> +	static u32 hs_fr_table_rB[][2] = {
> +		{UFS_HS_G1, 0x24},
> +		{UFS_HS_G2, 0x49},
> +	};
> +
> +	if (gear == 0) {
> +		dev_err(hba->dev, "%s: invalid gear = %d\n", __func__,
> gear);
> +		goto out_error;
> +	}
> +
> +	list_for_each_entry(clki, &hba->clk_list_head, list) {
> +		if (!strcmp(clki->name, "core_clk"))
> +			core_clk_rate = clk_get_rate(clki->clk);
> +	}
> +
> +	/* If frequency is smaller than 1MHz, set to 1MHz */
> +	if (core_clk_rate < DEFAULT_CLK_RATE_HZ)
> +		core_clk_rate = DEFAULT_CLK_RATE_HZ;
> +
> +	core_clk_cycles_per_us = core_clk_rate / USEC_PER_SEC;
> +	ufshcd_writel(hba, core_clk_cycles_per_us, REG_UFS_SYS1CLK_1US);
> +
> +	core_clk_period_in_ns = NSEC_PER_SEC / core_clk_rate;
> +	core_clk_period_in_ns <<= OFFSET_CLK_NS_REG;
> +	core_clk_period_in_ns &= MASK_CLK_NS_REG;
> +
> +	switch (hs) {
> +	case FASTAUTO_MODE:
> +	case FAST_MODE:
> +		if (rate == PA_HS_MODE_A) {
> +			if (gear > ARRAY_SIZE(hs_fr_table_rA)) {
> +				dev_err(hba->dev,
> +					"%s: index %d exceeds table size
> %zu\n",
> +					__func__, gear,
> +					ARRAY_SIZE(hs_fr_table_rA));
> +				goto out_error;
> +			}
> +			tx_clk_cycles_per_us = hs_fr_table_rA[gear-1][1];
> +		} else if (rate == PA_HS_MODE_B) {
> +			if (gear > ARRAY_SIZE(hs_fr_table_rB)) {
> +				dev_err(hba->dev,
> +					"%s: index %d exceeds table size
> %zu\n",
> +					__func__, gear,
> +					ARRAY_SIZE(hs_fr_table_rB));
> +				goto out_error;
> +			}
> +			tx_clk_cycles_per_us = hs_fr_table_rB[gear-1][1];
> +		} else {
> +			dev_err(hba->dev, "%s: invalid rate = %d\n",
> +				__func__, rate);
> +			goto out_error;
> +		}
> +		break;
> +	case SLOWAUTO_MODE:
> +	case SLOW_MODE:
> +		if (gear > ARRAY_SIZE(pwm_fr_table)) {
> +			dev_err(hba->dev,
> +					"%s: index %d exceeds table size
> %zu\n",
> +					__func__, gear,
> +					ARRAY_SIZE(pwm_fr_table));
> +			goto out_error;
> +		}
> +		tx_clk_cycles_per_us = pwm_fr_table[gear-1][1];
> +		break;
> +	case UNCHANGED:
> +	default:
> +		dev_err(hba->dev, "%s: invalid mode = %d\n", __func__,
> hs);
> +		goto out_error;
> +	}
> +
> +	/* this register 2 fields shall be written at once */
> +	ufshcd_writel(hba, core_clk_period_in_ns | tx_clk_cycles_per_us,
> +
> REG_UFS_TX_SYMBOL_CLK_NS_US);
> +	goto out;
> +
> +out_error:
> +	core_clk_rate = 0;
> +out:
> +	return core_clk_rate;
> +}
> +
> +static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, bool status)
> +{
> +	unsigned long core_clk_rate = 0;
> +	u32 core_clk_cycles_per_100ms;
> +
> +	switch (status) {
> +	case PRE_CHANGE:
> +		core_clk_rate = ufs_qcom_cfg_timers(hba, UFS_PWM_G1,
> +						    SLOWAUTO_MODE, 0);
> +		if (!core_clk_rate) {
> +			dev_err(hba->dev, "%s: ufs_qcom_cfg_timers()
> failed\n",
> +				__func__);
> +			return -EINVAL;
> +		}
> +		core_clk_cycles_per_100ms =
> +			(core_clk_rate / MSEC_PER_SEC) * 100;
> +		ufshcd_writel(hba, core_clk_cycles_per_100ms,
> +					REG_UFS_PA_LINK_STARTUP_TIMER);
> +		break;
> +	case POST_CHANGE:
> +		ufs_qcom_link_startup_post_change(hba);
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ufs_qcom_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
> +{
> +	struct ufs_qcom_host *host = hba->priv;
> +	struct phy *phy = host->generic_phy;
> +	int ret = 0;
> +
> +	if (ufs_qcom_is_link_off(hba)) {
> +		/*
> +		 * Disable the tx/rx lane symbol clocks before PHY is
> +		 * powered down as the PLL source should be disabled
> +		 * after downstream clocks are disabled.
> +		 */
> +		ufs_qcom_disable_lane_clks(host);
> +		phy_power_off(phy);
> +
> +		/* Assert PHY soft reset */
> +		ufs_qcom_assert_reset(hba);
> +		goto out;
> +	}
> +
> +	/*
> +	 * If UniPro link is not active, PHY ref_clk, main PHY analog
> power
> +	 * rail and low noise analog power rail for PLL can be switched
> off.
> +	 */
> +	if (!ufs_qcom_is_link_active(hba))
> +		phy_power_off(phy);
> +
> +out:
> +	return ret;
> +}
> +
> +static int ufs_qcom_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
> +{
> +	struct ufs_qcom_host *host = hba->priv;
> +	struct phy *phy = host->generic_phy;
> +	int err;
> +
> +	err = phy_power_on(phy);
> +	if (err) {
> +		dev_err(hba->dev, "%s: failed enabling regs, err = %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	hba->is_sys_suspended = false;
> +
> +out:
> +	return err;
> +}
> +
> +struct ufs_qcom_dev_params {
> +	u32 pwm_rx_gear;	/* pwm rx gear to work in */
> +	u32 pwm_tx_gear;	/* pwm tx gear to work in */
> +	u32 hs_rx_gear;		/* hs rx gear to work in */
> +	u32 hs_tx_gear;		/* hs tx gear to work in */
> +	u32 rx_lanes;		/* number of rx lanes */
> +	u32 tx_lanes;		/* number of tx lanes */
> +	u32 rx_pwr_pwm;		/* rx pwm working pwr */
> +	u32 tx_pwr_pwm;		/* tx pwm working pwr */
> +	u32 rx_pwr_hs;		/* rx hs working pwr */
> +	u32 tx_pwr_hs;		/* tx hs working pwr */
> +	u32 hs_rate;		/* rate A/B to work in HS */
> +	u32 desired_working_mode;
> +};
> +
> +static int ufs_qcom_get_pwr_dev_param(struct ufs_qcom_dev_params
> *qcom_param,
> +				      struct ufs_pa_layer_attr *dev_max,
> +				      struct ufs_pa_layer_attr
> *agreed_pwr)
> +{
> +	int min_qcom_gear;
> +	int min_dev_gear;
> +	bool is_dev_sup_hs = false;
> +	bool is_qcom_max_hs = false;
> +
> +	if (dev_max->pwr_rx == FAST_MODE)
> +		is_dev_sup_hs = true;
> +
> +	if (qcom_param->desired_working_mode == FAST) {
> +		is_qcom_max_hs = true;
> +		min_qcom_gear = min_t(u32, qcom_param->hs_rx_gear,
> +				      qcom_param->hs_tx_gear);
> +	} else {
> +		min_qcom_gear = min_t(u32, qcom_param->pwm_rx_gear,
> +				      qcom_param->pwm_tx_gear);
> +	}
> +
> +	/*
> +	 * device doesn't support HS but qcom_param->desired_working_mode
> is
> +	 * HS, thus device and qcom_param don't agree
> +	 */
> +	if (!is_dev_sup_hs && is_qcom_max_hs) {
> +		pr_err("%s: failed to agree on power mode (device doesn't
> support HS but requested power is HS)\n",
> +			__func__);
> +		return -ENOTSUPP;
> +	} else if (is_dev_sup_hs && is_qcom_max_hs) {
> +		/*
> +		 * since device supports HS, it supports FAST_MODE.
> +		 * since qcom_param->desired_working_mode is also HS
> +		 * then final decision (FAST/FASTAUTO) is done according
> +		 * to qcom_params as it is the restricting factor
> +		 */
> +		agreed_pwr->pwr_rx = agreed_pwr->pwr_tx =
> +						qcom_param->rx_pwr_hs;
> +	} else {
> +		/*
> +		 * here qcom_param->desired_working_mode is PWM.
> +		 * it doesn't matter whether device supports HS or PWM,
> +		 * in both cases qcom_param->desired_working_mode will
> +		 * determine the mode
> +		 */
> +		 agreed_pwr->pwr_rx = agreed_pwr->pwr_tx =
> +						qcom_param->rx_pwr_pwm;
> +	}
> +
> +	/*
> +	 * we would like tx to work in the minimum number of lanes
> +	 * between device capability and vendor preferences.
> +	 * the same decision will be made for rx
> +	 */
> +	agreed_pwr->lane_tx = min_t(u32, dev_max->lane_tx,
> +						qcom_param->tx_lanes);
> +	agreed_pwr->lane_rx = min_t(u32, dev_max->lane_rx,
> +						qcom_param->rx_lanes);
> +
> +	/* device maximum gear is the minimum between device rx and tx
> gears */
> +	min_dev_gear = min_t(u32, dev_max->gear_rx, dev_max->gear_tx);
> +
> +	/*
> +	 * if both device capabilities and vendor pre-defined preferences
> are
> +	 * both HS or both PWM then set the minimum gear to be the chosen
> +	 * working gear.
> +	 * if one is PWM and one is HS then the one that is PWM get to
> decide
> +	 * what is the gear, as it is the one that also decided previously
> what
> +	 * pwr the device will be configured to.
> +	 */
> +	if ((is_dev_sup_hs && is_qcom_max_hs) ||
> +	    (!is_dev_sup_hs && !is_qcom_max_hs))
> +		agreed_pwr->gear_rx = agreed_pwr->gear_tx =
> +			min_t(u32, min_dev_gear, min_qcom_gear);
> +	else if (!is_dev_sup_hs)
> +		agreed_pwr->gear_rx = agreed_pwr->gear_tx = min_dev_gear;
> +	else
> +		agreed_pwr->gear_rx = agreed_pwr->gear_tx = min_qcom_gear;
> +
> +	agreed_pwr->hs_rate = qcom_param->hs_rate;
> +	return 0;
> +}
> +
> +static int ufs_qcom_update_bus_bw_vote(struct ufs_qcom_host *host)
> +{
> +	int vote;
> +	int err = 0;
> +	char mode[BUS_VECTOR_NAME_LEN];
> +
> +	ufs_qcom_get_speed_mode(&host->dev_req_params, mode);
> +
> +	vote = ufs_qcom_get_bus_vote(host, mode);
> +	if (vote >= 0)
> +		err = ufs_qcom_set_bus_vote(host, vote);
> +	else
> +		err = vote;
> +
> +	if (err)
> +		dev_err(host->hba->dev, "%s: failed %d\n", __func__, err);
> +	else
> +		host->bus_vote.saved_vote = vote;
> +	return err;
> +}
> +
> +static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba,
> +				bool status,
> +				struct ufs_pa_layer_attr *dev_max_params,
> +				struct ufs_pa_layer_attr *dev_req_params)
> +{
> +	u32 val;
> +	struct ufs_qcom_host *host = hba->priv;
> +	struct phy *phy = host->generic_phy;
> +	struct ufs_qcom_dev_params ufs_qcom_cap;
> +	int ret = 0;
> +	int res = 0;
> +
> +	if (!dev_req_params) {
> +		pr_err("%s: incoming dev_req_params is NULL\n", __func__);
> +		ret = -EINVAL;
> +		goto out;
> +	}
> +
> +	switch (status) {
> +	case PRE_CHANGE:
> +		ufs_qcom_cap.tx_lanes = UFS_QCOM_LIMIT_NUM_LANES_TX;
> +		ufs_qcom_cap.rx_lanes = UFS_QCOM_LIMIT_NUM_LANES_RX;
> +		ufs_qcom_cap.hs_rx_gear = UFS_QCOM_LIMIT_HSGEAR_RX;
> +		ufs_qcom_cap.hs_tx_gear = UFS_QCOM_LIMIT_HSGEAR_TX;
> +		ufs_qcom_cap.pwm_rx_gear = UFS_QCOM_LIMIT_PWMGEAR_RX;
> +		ufs_qcom_cap.pwm_tx_gear = UFS_QCOM_LIMIT_PWMGEAR_TX;
> +		ufs_qcom_cap.rx_pwr_pwm = UFS_QCOM_LIMIT_RX_PWR_PWM;
> +		ufs_qcom_cap.tx_pwr_pwm = UFS_QCOM_LIMIT_TX_PWR_PWM;
> +		ufs_qcom_cap.rx_pwr_hs = UFS_QCOM_LIMIT_RX_PWR_HS;
> +		ufs_qcom_cap.tx_pwr_hs = UFS_QCOM_LIMIT_TX_PWR_HS;
> +		ufs_qcom_cap.hs_rate = UFS_QCOM_LIMIT_HS_RATE;
> +		ufs_qcom_cap.desired_working_mode =
> +					UFS_QCOM_LIMIT_DESIRED_MODE;
> +
> +		ret = ufs_qcom_get_pwr_dev_param(&ufs_qcom_cap,
> +						 dev_max_params,
> +						 dev_req_params);
> +		if (ret) {
> +			pr_err("%s: failed to determine capabilities\n",
> +					__func__);
> +			goto out;
> +		}
> +
> +		break;
> +	case POST_CHANGE:
> +		if (!ufs_qcom_cfg_timers(hba, dev_req_params->gear_rx,
> +					dev_req_params->pwr_rx,
> +					dev_req_params->hs_rate)) {
> +			dev_err(hba->dev, "%s: ufs_qcom_cfg_timers()
> failed\n",
> +				__func__);
> +			/*
> +			 * we return error code at the end of the routine,
> +			 * but continue to configure
> UFS_PHY_TX_LANE_ENABLE
> +			 * and bus voting as usual
> +			 */
> +			ret = -EINVAL;
> +		}
> +
> +		val = ~(MAX_U32 << dev_req_params->lane_tx);
> +		res = ufs_qcom_phy_set_tx_lane_enable(phy, val);
> +		if (res) {
> +			dev_err(hba->dev, "%s:
> ufs_qcom_phy_set_tx_lane_enable() failed res = %d\n",
> +				__func__, res);
> +			ret = res;
> +		}
> +
> +		/* cache the power mode parameters to use internally */
> +		memcpy(&host->dev_req_params,
> +				dev_req_params, sizeof(*dev_req_params));
> +		ufs_qcom_update_bus_bw_vote(host);
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		break;
> +	}
> +out:
> +	return ret;
> +}
> +
> +/**
> + * ufs_qcom_advertise_quirks - advertise the known QCOM UFS controller
> quirks
> + * @hba: host controller instance
> + *
> + * QCOM UFS host controller might have some non standard behaviours
> (quirks)
> + * than what is specified by UFSHCI specification. Advertise all such
> + * quirks to standard UFS host controller driver so standard takes them
> into
> + * account.
> + */
> +static void ufs_qcom_advertise_quirks(struct ufs_hba *hba)
> +{
> +	u8 major;
> +	u16 minor, step;
> +
> +	ufs_qcom_get_controller_revision(hba, &major, &minor, &step);
> +
> +	/*
> +	 * TBD
> +	 * here we should be advertising controller quirks according to
> +	 * controller version.
> +	 */
> +}
> +
> +static int ufs_qcom_get_bus_vote(struct ufs_qcom_host *host,
> +		const char *speed_mode)
> +{
> +	struct device *dev = host->hba->dev;
> +	struct device_node *np = dev->of_node;
> +	int err;
> +	const char *key = "qcom,bus-vector-names";
> +
> +	if (!speed_mode) {
> +		err = -EINVAL;
> +		goto out;
> +	}
> +
> +	if (host->bus_vote.is_max_bw_needed && !!strcmp(speed_mode,
> "MIN"))
> +		err = of_property_match_string(np, key, "MAX");
> +	else
> +		err = of_property_match_string(np, key, speed_mode);
> +
> +out:
> +	if (err < 0)
> +		dev_err(dev, "%s: Invalid %s mode %d\n",
> +				__func__, speed_mode, err);
> +	return err;
> +}
> +
> +static int ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote)
> +{
> +	int err = 0;
> +
> +	if (vote != host->bus_vote.curr_vote)
> +		host->bus_vote.curr_vote = vote;
> +
> +	return err;
> +}
> +
> +static void ufs_qcom_get_speed_mode(struct ufs_pa_layer_attr *p, char
> *result)
> +{
> +	int gear = max_t(u32, p->gear_rx, p->gear_tx);
> +	int lanes = max_t(u32, p->lane_rx, p->lane_tx);
> +	int pwr;
> +
> +	/* default to PWM Gear 1, Lane 1 if power mode is not initialized
> */
> +	if (!gear)
> +		gear = 1;
> +
> +	if (!lanes)
> +		lanes = 1;
> +
> +	if (!p->pwr_rx && !p->pwr_tx) {
> +		pwr = SLOWAUTO_MODE;
> +		snprintf(result, BUS_VECTOR_NAME_LEN, "MIN");
> +	} else if (p->pwr_rx == FAST_MODE || p->pwr_rx == FASTAUTO_MODE ||
> +		 p->pwr_tx == FAST_MODE || p->pwr_tx == FASTAUTO_MODE) {
> +		pwr = FAST_MODE;
> +		snprintf(result, BUS_VECTOR_NAME_LEN, "%s_R%s_G%d_L%d",
> "HS",
> +			 p->hs_rate == PA_HS_MODE_B ? "B" : "A", gear,
> lanes);
> +	} else {
> +		pwr = SLOW_MODE;
> +		snprintf(result, BUS_VECTOR_NAME_LEN, "%s_G%d_L%d",
> +			 "PWM", gear, lanes);
> +	}
> +}
> +
> +static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on)
> +{
> +	struct ufs_qcom_host *host = hba->priv;
> +	int err = 0;
> +	int vote = 0;
> +
> +	/*
> +	 * In case ufs_qcom_init() is not yet done, simply ignore.
> +	 * This ufs_qcom_setup_clocks() shall be called from
> +	 * ufs_qcom_init() after init is done.
> +	 */
> +	if (!host)
> +		return 0;
> +
> +	if (on) {
> +		err = ufs_qcom_phy_enable_iface_clk(host->generic_phy);
> +		if (err)
> +			goto out;
> +
> +		err = ufs_qcom_phy_enable_ref_clk(host->generic_phy);
> +		if (err) {
> +			dev_err(hba->dev, "%s enable phy ref clock failed,
> err=%d\n",
> +				__func__, err);
> +			ufs_qcom_phy_disable_iface_clk(host->generic_phy);
> +			goto out;
> +		}
> +		/* enable the device ref clock */
> +		ufs_qcom_phy_enable_dev_ref_clk(host->generic_phy);
> +		vote = host->bus_vote.saved_vote;
> +		if (vote == host->bus_vote.min_bw_vote)
> +			ufs_qcom_update_bus_bw_vote(host);
> +	} else {
> +		/* M-PHY RMMI interface clocks can be turned off */
> +		ufs_qcom_phy_disable_iface_clk(host->generic_phy);
> +		if (!ufs_qcom_is_link_active(hba)) {
> +			/* turn off UFS local PHY ref_clk */
> +			ufs_qcom_phy_disable_ref_clk(host->generic_phy);
> +			/* disable device ref_clk */
> +
> ufs_qcom_phy_disable_dev_ref_clk(host->generic_phy);
> +		}
> +		vote = host->bus_vote.min_bw_vote;
> +	}
> +
> +	err = ufs_qcom_set_bus_vote(host, vote);
> +	if (err)
> +		dev_err(hba->dev, "%s: set bus vote failed %d\n",
> +				__func__, err);
> +
> +out:
> +	return err;
> +}
> +
> +static ssize_t
> +show_ufs_to_mem_max_bus_bw(struct device *dev, struct device_attribute
> *attr,
> +			char *buf)
> +{
> +	struct ufs_hba *hba = dev_get_drvdata(dev);
> +	struct ufs_qcom_host *host = hba->priv;
> +
> +	return snprintf(buf, PAGE_SIZE, "%u\n",
> +			host->bus_vote.is_max_bw_needed);
> +}
> +
> +static ssize_t
> +store_ufs_to_mem_max_bus_bw(struct device *dev, struct device_attribute
> *attr,
> +		const char *buf, size_t count)
> +{
> +	struct ufs_hba *hba = dev_get_drvdata(dev);
> +	struct ufs_qcom_host *host = hba->priv;
> +	uint32_t value;
> +
> +	if (!kstrtou32(buf, 0, &value)) {
> +		host->bus_vote.is_max_bw_needed = !!value;
> +		ufs_qcom_update_bus_bw_vote(host);
> +	}
> +
> +	return count;
> +}
> +
> +static int ufs_qcom_bus_register(struct ufs_qcom_host *host)
> +{
> +	int err;
> +	struct device *dev = host->hba->dev;
> +	struct device_node *np = dev->of_node;
> +
> +	err = of_property_count_strings(np, "qcom,bus-vector-names");
> +	if (err < 0 ) {
> +		dev_err(dev, "%s: qcom,bus-vector-names not specified
> correctly %d\n",
> +				__func__, err);
> +		goto out;
> +	}
> +
> +	/* cache the vote index for minimum and maximum bandwidth */
> +	host->bus_vote.min_bw_vote = ufs_qcom_get_bus_vote(host, "MIN");
> +	host->bus_vote.max_bw_vote = ufs_qcom_get_bus_vote(host, "MAX");
> +
> +	host->bus_vote.max_bus_bw.show = show_ufs_to_mem_max_bus_bw;
> +	host->bus_vote.max_bus_bw.store = store_ufs_to_mem_max_bus_bw;
> +	sysfs_attr_init(&host->bus_vote.max_bus_bw.attr);
> +	host->bus_vote.max_bus_bw.attr.name = "max_bus_bw";
> +	host->bus_vote.max_bus_bw.attr.mode = S_IRUGO | S_IWUSR;
> +	err = device_create_file(dev, &host->bus_vote.max_bus_bw);
> +out:
> +	return err;
> +}
> +
> +#define	ANDROID_BOOT_DEV_MAX	30
> +static char android_boot_dev[ANDROID_BOOT_DEV_MAX];
> +static int get_android_boot_dev(char *str)
> +{
> +	strlcpy(android_boot_dev, str, ANDROID_BOOT_DEV_MAX);
> +	return 1;
> +}
> +__setup("androidboot.bootdevice=", get_android_boot_dev);
> +
> +/**
> + * ufs_qcom_init - bind phy with controller
> + * @hba: host controller instance
> + *
> + * Binds PHY with controller and powers up PHY enabling clocks
> + * and regulators.
> + *
> + * Returns -EPROBE_DEFER if binding fails, returns negative error
> + * on phy power up failure and returns zero on success.
> + */
> +static int ufs_qcom_init(struct ufs_hba *hba)
> +{
> +	int err;
> +	struct device *dev = hba->dev;
> +	struct ufs_qcom_host *host;
> +
> +	if (strlen(android_boot_dev) && strcmp(android_boot_dev,
> dev_name(dev)))
> +		return -ENODEV;
> +
> +	host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
> +	if (!host) {
> +		err = -ENOMEM;
> +		dev_err(dev, "%s: no memory for qcom ufs host\n",
> __func__);
> +		goto out;
> +	}
> +
> +	host->hba = hba;
> +	hba->priv = (void *)host;
> +
> +	host->generic_phy = devm_phy_get(dev, "ufsphy");
> +
> +	if (IS_ERR(host->generic_phy)) {
> +		err = PTR_ERR(host->generic_phy);
> +		dev_err(dev, "%s: PHY get failed %d\n", __func__, err);
> +		goto out;
> +	}
> +
> +	err = ufs_qcom_bus_register(host);
> +	if (err)
> +		goto out_host_free;
> +
> +	phy_init(host->generic_phy);
> +	err = phy_power_on(host->generic_phy);
> +	if (err)
> +		goto out_unregister_bus;
> +
> +	err = ufs_qcom_init_lane_clks(host);
> +	if (err)
> +		goto out_disable_phy;
> +
> +	ufs_qcom_advertise_quirks(hba);
> +
> +	hba->caps |= UFSHCD_CAP_CLK_GATING | UFSHCD_CAP_CLK_SCALING;
> +	hba->caps |= UFSHCD_CAP_AUTO_BKOPS_SUSPEND;
> +
> +	ufs_qcom_setup_clocks(hba, true);
> +
> +	if (hba->dev->id < MAX_UFS_QCOM_HOSTS)
> +		ufs_qcom_hosts[hba->dev->id] = host;
> +
> +	goto out;
> +
> +out_disable_phy:
> +	phy_power_off(host->generic_phy);
> +out_unregister_bus:
> +	phy_exit(host->generic_phy);
> +out_host_free:
> +	devm_kfree(dev, host);
> +	hba->priv = NULL;
> +out:
> +	return err;
> +}
> +
> +static void ufs_qcom_exit(struct ufs_hba *hba)
> +{
> +	struct ufs_qcom_host *host = hba->priv;
> +
> +	ufs_qcom_disable_lane_clks(host);
> +	phy_power_off(host->generic_phy);
> +}
> +
> +static
> +void ufs_qcom_clk_scale_notify(struct ufs_hba *hba)
> +{
> +	struct ufs_qcom_host *host = hba->priv;
> +	struct ufs_pa_layer_attr *dev_req_params = &host->dev_req_params;
> +
> +	if (!dev_req_params)
> +		return;
> +
> +	ufs_qcom_cfg_timers(hba, dev_req_params->gear_rx,
> +				dev_req_params->pwr_rx,
> +				dev_req_params->hs_rate);
> +}
> +
> +/**
> + * struct ufs_hba_qcom_vops - UFS QCOM specific variant operations
> + *
> + * The variant operations configure the necessary controller and PHY
> + * handshake during initialization.
> + */
> +static const struct ufs_hba_variant_ops ufs_hba_qcom_vops = {
> +	.name                   = "qcom",
> +	.init                   = ufs_qcom_init,
> +	.exit                   = ufs_qcom_exit,
> +	.clk_scale_notify	= ufs_qcom_clk_scale_notify,
> +	.setup_clocks           = ufs_qcom_setup_clocks,
> +	.hce_enable_notify      = ufs_qcom_hce_enable_notify,
> +	.link_startup_notify    = ufs_qcom_link_startup_notify,
> +	.pwr_change_notify	= ufs_qcom_pwr_change_notify,
> +	.suspend		= ufs_qcom_suspend,
> +	.resume			= ufs_qcom_resume,
> +};
> +EXPORT_SYMBOL(ufs_hba_qcom_vops);
> diff --git a/drivers/scsi/ufs/ufs-qcom.h b/drivers/scsi/ufs/ufs-qcom.h
> new file mode 100644
> index 0000000..9a6febd
> --- /dev/null
> +++ b/drivers/scsi/ufs/ufs-qcom.h
> @@ -0,0 +1,170 @@
> +/* Copyright (c) 2013-2015, 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.
> + *
> + */
> +
> +#ifndef UFS_QCOM_H_
> +#define UFS_QCOM_H_
> +
> +#define MAX_UFS_QCOM_HOSTS	1
> +#define MAX_U32                 (~(u32)0)
> +#define MPHY_TX_FSM_STATE       0x41
> +#define TX_FSM_HIBERN8          0x1
> +#define HBRN8_POLL_TOUT_MS      100
> +#define DEFAULT_CLK_RATE_HZ     1000000
> +#define BUS_VECTOR_NAME_LEN     32
> +
> +#define UFS_HW_VER_MAJOR_SHFT	(28)
> +#define UFS_HW_VER_MAJOR_MASK	(0x000F << UFS_HW_VER_MAJOR_SHFT)
> +#define UFS_HW_VER_MINOR_SHFT	(16)
> +#define UFS_HW_VER_MINOR_MASK	(0x0FFF << UFS_HW_VER_MINOR_SHFT)
> +#define UFS_HW_VER_STEP_SHFT	(0)
> +#define UFS_HW_VER_STEP_MASK	(0xFFFF << UFS_HW_VER_STEP_SHFT)
> +
> +/* vendor specific pre-defined parameters */
> +#define SLOW 1
> +#define FAST 2
> +
> +#define UFS_QCOM_LIMIT_NUM_LANES_RX	2
> +#define UFS_QCOM_LIMIT_NUM_LANES_TX	2
> +#define UFS_QCOM_LIMIT_HSGEAR_RX	UFS_HS_G2
> +#define UFS_QCOM_LIMIT_HSGEAR_TX	UFS_HS_G2
> +#define UFS_QCOM_LIMIT_PWMGEAR_RX	UFS_PWM_G4
> +#define UFS_QCOM_LIMIT_PWMGEAR_TX	UFS_PWM_G4
> +#define UFS_QCOM_LIMIT_RX_PWR_PWM	SLOW_MODE
> +#define UFS_QCOM_LIMIT_TX_PWR_PWM	SLOW_MODE
> +#define UFS_QCOM_LIMIT_RX_PWR_HS	FAST_MODE
> +#define UFS_QCOM_LIMIT_TX_PWR_HS	FAST_MODE
> +#define UFS_QCOM_LIMIT_HS_RATE		PA_HS_MODE_B
> +#define UFS_QCOM_LIMIT_DESIRED_MODE	FAST
> +
> +/* QCOM UFS host controller vendor specific registers */
> +enum {
> +	REG_UFS_SYS1CLK_1US                 = 0xC0,
> +	REG_UFS_TX_SYMBOL_CLK_NS_US         = 0xC4,
> +	REG_UFS_LOCAL_PORT_ID_REG           = 0xC8,
> +	REG_UFS_PA_ERR_CODE                 = 0xCC,
> +	REG_UFS_RETRY_TIMER_REG             = 0xD0,
> +	REG_UFS_PA_LINK_STARTUP_TIMER       = 0xD8,
> +	REG_UFS_CFG1                        = 0xDC,
> +	REG_UFS_CFG2                        = 0xE0,
> +	REG_UFS_HW_VERSION                  = 0xE4,
> +
> +	UFS_DBG_RD_REG_UAWM			= 0x100,
> +	UFS_DBG_RD_REG_UARM			= 0x200,
> +	UFS_DBG_RD_REG_TXUC			= 0x300,
> +	UFS_DBG_RD_REG_RXUC			= 0x400,
> +	UFS_DBG_RD_REG_DFC			= 0x500,
> +	UFS_DBG_RD_REG_TRLUT			= 0x600,
> +	UFS_DBG_RD_REG_TMRLUT			= 0x700,
> +	UFS_UFS_DBG_RD_REG_OCSC			= 0x800,
> +
> +	UFS_UFS_DBG_RD_DESC_RAM			= 0x1500,
> +	UFS_UFS_DBG_RD_PRDT_RAM			= 0x1700,
> +	UFS_UFS_DBG_RD_RESP_RAM			= 0x1800,
> +	UFS_UFS_DBG_RD_EDTL_RAM			= 0x1900,
> +};
> +
> +/* bit definitions for REG_UFS_CFG2 register */
> +#define UAWM_HW_CGC_EN		(1 << 0)
> +#define UARM_HW_CGC_EN		(1 << 1)
> +#define TXUC_HW_CGC_EN		(1 << 2)
> +#define RXUC_HW_CGC_EN		(1 << 3)
> +#define DFC_HW_CGC_EN		(1 << 4)
> +#define TRLUT_HW_CGC_EN		(1 << 5)
> +#define TMRLUT_HW_CGC_EN	(1 << 6)
> +#define OCSC_HW_CGC_EN		(1 << 7)
> +
> +#define REG_UFS_CFG2_CGC_EN_ALL (UAWM_HW_CGC_EN | UARM_HW_CGC_EN |\
> +				 TXUC_HW_CGC_EN | RXUC_HW_CGC_EN |\
> +				 DFC_HW_CGC_EN | TRLUT_HW_CGC_EN |\
> +				 TMRLUT_HW_CGC_EN | OCSC_HW_CGC_EN)
> +
> +/* bit offset */
> +enum {
> +	OFFSET_UFS_PHY_SOFT_RESET           = 1,
> +	OFFSET_CLK_NS_REG                   = 10,
> +};
> +
> +/* bit masks */
> +enum {
> +	MASK_UFS_PHY_SOFT_RESET             = 0x2,
> +	MASK_TX_SYMBOL_CLK_1US_REG          = 0x3FF,
> +	MASK_CLK_NS_REG                     = 0xFFFC00,
> +};
> +
> +enum ufs_qcom_phy_init_type {
> +	UFS_PHY_INIT_FULL,
> +	UFS_PHY_INIT_CFG_RESTORE,
> +};
> +
> +static inline void
> +ufs_qcom_get_controller_revision(struct ufs_hba *hba,
> +				 u8 *major, u16 *minor, u16 *step)
> +{
> +	u32 ver = ufshcd_readl(hba, REG_UFS_HW_VERSION);
> +
> +	*major = (ver & UFS_HW_VER_MAJOR_MASK) >> UFS_HW_VER_MAJOR_SHFT;
> +	*minor = (ver & UFS_HW_VER_MINOR_MASK) >> UFS_HW_VER_MINOR_SHFT;
> +	*step = (ver & UFS_HW_VER_STEP_MASK) >> UFS_HW_VER_STEP_SHFT;
> +};
> +
> +static inline void ufs_qcom_assert_reset(struct ufs_hba *hba)
> +{
> +	ufshcd_rmwl(hba, MASK_UFS_PHY_SOFT_RESET,
> +			1 << OFFSET_UFS_PHY_SOFT_RESET, REG_UFS_CFG1);
> +
> +	/*
> +	 * Make sure assertion of ufs phy reset is written to
> +	 * register before returning
> +	 */
> +	mb();
> +}
> +
> +static inline void ufs_qcom_deassert_reset(struct ufs_hba *hba)
> +{
> +	ufshcd_rmwl(hba, MASK_UFS_PHY_SOFT_RESET,
> +			0 << OFFSET_UFS_PHY_SOFT_RESET, REG_UFS_CFG1);
> +
> +	/*
> +	 * Make sure de-assertion of ufs phy reset is written to
> +	 * register before returning
> +	 */
> +	mb();
> +}
> +
> +struct ufs_qcom_bus_vote {
> +	uint32_t client_handle;
> +	uint32_t curr_vote;
> +	int min_bw_vote;
> +	int max_bw_vote;
> +	int saved_vote;
> +	bool is_max_bw_needed;
> +	struct device_attribute max_bus_bw;
> +};
> +
> +struct ufs_qcom_host {
> +	struct phy *generic_phy;
> +	struct ufs_hba *hba;
> +	struct ufs_qcom_bus_vote bus_vote;
> +	struct ufs_pa_layer_attr dev_req_params;
> +	struct clk *rx_l0_sync_clk;
> +	struct clk *tx_l0_sync_clk;
> +	struct clk *rx_l1_sync_clk;
> +	struct clk *tx_l1_sync_clk;
> +	bool is_lane_clks_enabled;
> +};
> +
> +#define ufs_qcom_is_link_off(hba) ufshcd_is_link_off(hba)
> +#define ufs_qcom_is_link_active(hba) ufshcd_is_link_active(hba)
> +#define ufs_qcom_is_link_hibern8(hba) ufshcd_is_link_hibern8(hba)
> +
> +#endif /* UFS_QCOM_H_ */
> --
> 1.8.5.2
>
> --
> QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
> of Code Aurora Forum, hosted by The Linux Foundation
> --
> To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v7 3/5] scsi: ufs-qcom: add support for Qualcomm Technologies Inc platforms
@ 2015-01-15 15:20     ` Dov Levenglick
  0 siblings, 0 replies; 27+ messages in thread
From: Dov Levenglick @ 2015-01-15 15:20 UTC (permalink / raw)
  To: Yaniv Gardi
  Cc: james.bottomley, hch, linux-kernel, linux-scsi, linux-arm-msm,
	santoshsy, linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Vinayak Holikatti, James E.J. Bottomley

Reviewed-by: Dov Levenglick <dovl@codeaurora.org>

> This change adds support for Qualcomm Technologies Inc platforms that
> use UFS driver. for example, it adds :
> - PM specific operations during hibern8, suspend, resume, clock setup
> - qcom-ufs generic phy driver initialization, calibration,
>   power-on/off sequence, etc.
> - UFS Controller specific configuration
> - Rate, Gear, Mode negotiation between device and controller
>
> Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>
>
> ---
>  drivers/scsi/ufs/Kconfig    |   13 +
>  drivers/scsi/ufs/Makefile   |    1 +
>  drivers/scsi/ufs/ufs-qcom.c | 1004
> +++++++++++++++++++++++++++++++++++++++++++
>  drivers/scsi/ufs/ufs-qcom.h |  170 ++++++++
>  4 files changed, 1188 insertions(+)
>  create mode 100644 drivers/scsi/ufs/ufs-qcom.c
>  create mode 100644 drivers/scsi/ufs/ufs-qcom.h
>
> diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig
> index 6e07b2a..8a1f4b3 100644
> --- a/drivers/scsi/ufs/Kconfig
> +++ b/drivers/scsi/ufs/Kconfig
> @@ -70,3 +70,16 @@ config SCSI_UFSHCD_PLATFORM
>  	If you have a controller with this interface, say Y or M here.
>
>  	  If unsure, say N.
> +
> +config SCSI_UFS_QCOM
> +	bool "QCOM specific hooks to UFS controller platform driver"
> +	depends on SCSI_UFSHCD_PLATFORM && ARCH_MSM
> +	select PHY_QCOM_UFS
> +	help
> +	  This selects the QCOM specific additions to UFSHCD platform
> driver.
> +	  UFS host on QCOM needs some vendor specific configuration before
> +	  accessing the hardware which includes PHY configuration and
> vendor
> +	  specific registers.
> +
> +	  Select this if you have UFS controller on QCOM chipset.
> +	  If unsure, say N.
> diff --git a/drivers/scsi/ufs/Makefile b/drivers/scsi/ufs/Makefile
> index 1e5bd48..8303bcc 100644
> --- a/drivers/scsi/ufs/Makefile
> +++ b/drivers/scsi/ufs/Makefile
> @@ -1,4 +1,5 @@
>  # UFSHCD makefile
> +obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-qcom.o
>  obj-$(CONFIG_SCSI_UFSHCD) += ufshcd.o
>  obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o
>  obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o
> diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c
> new file mode 100644
> index 0000000..9217af9
> --- /dev/null
> +++ b/drivers/scsi/ufs/ufs-qcom.c
> @@ -0,0 +1,1004 @@
> +/*
> + * Copyright (c) 2013-2015, 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/time.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/phy/phy.h>
> +
> +#include <linux/phy/phy-qcom-ufs.h>
> +#include "ufshcd.h"
> +#include "unipro.h"
> +#include "ufs-qcom.h"
> +#include "ufshci.h"
> +
> +static struct ufs_qcom_host *ufs_qcom_hosts[MAX_UFS_QCOM_HOSTS];
> +
> +static void ufs_qcom_get_speed_mode(struct ufs_pa_layer_attr *p, char
> *result);
> +static int ufs_qcom_get_bus_vote(struct ufs_qcom_host *host,
> +		const char *speed_mode);
> +static int ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote);
> +
> +static int ufs_qcom_get_connected_tx_lanes(struct ufs_hba *hba, u32
> *tx_lanes)
> +{
> +	int err = 0;
> +
> +	err = ufshcd_dme_get(hba,
> +			UIC_ARG_MIB(PA_CONNECTEDTXDATALANES), tx_lanes);
> +	if (err)
> +		dev_err(hba->dev, "%s: couldn't read
> PA_CONNECTEDTXDATALANES %d\n",
> +				__func__, err);
> +
> +	return err;
> +}
> +
> +static int ufs_qcom_host_clk_get(struct device *dev,
> +		const char *name, struct clk **clk_out)
> +{
> +	struct clk *clk;
> +	int err = 0;
> +
> +	clk = devm_clk_get(dev, name);
> +	if (IS_ERR(clk)) {
> +		err = PTR_ERR(clk);
> +		dev_err(dev, "%s: failed to get %s err %d",
> +				__func__, name, err);
> +	} else {
> +		*clk_out = clk;
> +	}
> +
> +	return err;
> +}
> +
> +static int ufs_qcom_host_clk_enable(struct device *dev,
> +		const char *name, struct clk *clk)
> +{
> +	int err = 0;
> +
> +	err = clk_prepare_enable(clk);
> +	if (err)
> +		dev_err(dev, "%s: %s enable failed %d\n", __func__, name,
> err);
> +
> +	return err;
> +}
> +
> +static void ufs_qcom_disable_lane_clks(struct ufs_qcom_host *host)
> +{
> +	if (!host->is_lane_clks_enabled)
> +		return;
> +
> +	clk_disable_unprepare(host->tx_l1_sync_clk);
> +	clk_disable_unprepare(host->tx_l0_sync_clk);
> +	clk_disable_unprepare(host->rx_l1_sync_clk);
> +	clk_disable_unprepare(host->rx_l0_sync_clk);
> +
> +	host->is_lane_clks_enabled = false;
> +}
> +
> +static int ufs_qcom_enable_lane_clks(struct ufs_qcom_host *host)
> +{
> +	int err = 0;
> +	struct device *dev = host->hba->dev;
> +
> +	if (host->is_lane_clks_enabled)
> +		return 0;
> +
> +	err = ufs_qcom_host_clk_enable(dev, "rx_lane0_sync_clk",
> +		host->rx_l0_sync_clk);
> +	if (err)
> +		goto out;
> +
> +	err = ufs_qcom_host_clk_enable(dev, "tx_lane0_sync_clk",
> +		host->tx_l0_sync_clk);
> +	if (err)
> +		goto disable_rx_l0;
> +
> +	err = ufs_qcom_host_clk_enable(dev, "rx_lane1_sync_clk",
> +		host->rx_l1_sync_clk);
> +	if (err)
> +		goto disable_tx_l0;
> +
> +	err = ufs_qcom_host_clk_enable(dev, "tx_lane1_sync_clk",
> +		host->tx_l1_sync_clk);
> +	if (err)
> +		goto disable_rx_l1;
> +
> +	host->is_lane_clks_enabled = true;
> +	goto out;
> +
> +disable_rx_l1:
> +	clk_disable_unprepare(host->rx_l1_sync_clk);
> +disable_tx_l0:
> +	clk_disable_unprepare(host->tx_l0_sync_clk);
> +disable_rx_l0:
> +	clk_disable_unprepare(host->rx_l0_sync_clk);
> +out:
> +	return err;
> +}
> +
> +static int ufs_qcom_init_lane_clks(struct ufs_qcom_host *host)
> +{
> +	int err = 0;
> +	struct device *dev = host->hba->dev;
> +
> +	err = ufs_qcom_host_clk_get(dev,
> +			"rx_lane0_sync_clk", &host->rx_l0_sync_clk);
> +	if (err)
> +		goto out;
> +
> +	err = ufs_qcom_host_clk_get(dev,
> +			"tx_lane0_sync_clk", &host->tx_l0_sync_clk);
> +	if (err)
> +		goto out;
> +
> +	err = ufs_qcom_host_clk_get(dev, "rx_lane1_sync_clk",
> +		&host->rx_l1_sync_clk);
> +	if (err)
> +		goto out;
> +
> +	err = ufs_qcom_host_clk_get(dev, "tx_lane1_sync_clk",
> +		&host->tx_l1_sync_clk);
> +out:
> +	return err;
> +}
> +
> +static int ufs_qcom_link_startup_post_change(struct ufs_hba *hba)
> +{
> +	struct ufs_qcom_host *host = hba->priv;
> +	struct phy *phy = host->generic_phy;
> +	u32 tx_lanes;
> +	int err = 0;
> +
> +	err = ufs_qcom_get_connected_tx_lanes(hba, &tx_lanes);
> +	if (err)
> +		goto out;
> +
> +	err = ufs_qcom_phy_set_tx_lane_enable(phy, tx_lanes);
> +	if (err)
> +		dev_err(hba->dev, "%s: ufs_qcom_phy_set_tx_lane_enable
> failed\n",
> +			__func__);
> +
> +out:
> +	return err;
> +}
> +
> +static int ufs_qcom_check_hibern8(struct ufs_hba *hba)
> +{
> +	int err;
> +	u32 tx_fsm_val = 0;
> +	unsigned long timeout = jiffies +
> msecs_to_jiffies(HBRN8_POLL_TOUT_MS);
> +
> +	do {
> +		err = ufshcd_dme_get(hba,
> +			UIC_ARG_MIB(MPHY_TX_FSM_STATE), &tx_fsm_val);
> +		if (err || tx_fsm_val == TX_FSM_HIBERN8)
> +			break;
> +
> +		/* sleep for max. 200us */
> +		usleep_range(100, 200);
> +	} while (time_before(jiffies, timeout));
> +
> +	/*
> +	 * we might have scheduled out for long during polling so
> +	 * check the state again.
> +	 */
> +	if (time_after(jiffies, timeout))
> +		err = ufshcd_dme_get(hba,
> +				UIC_ARG_MIB(MPHY_TX_FSM_STATE),
> &tx_fsm_val);
> +
> +	if (err) {
> +		dev_err(hba->dev, "%s: unable to get TX_FSM_STATE, err
> %d\n",
> +				__func__, err);
> +	} else if (tx_fsm_val != TX_FSM_HIBERN8) {
> +		err = tx_fsm_val;
> +		dev_err(hba->dev, "%s: invalid TX_FSM_STATE = %d\n",
> +				__func__, err);
> +	}
> +
> +	return err;
> +}
> +
> +static int ufs_qcom_power_up_sequence(struct ufs_hba *hba)
> +{
> +	struct ufs_qcom_host *host = hba->priv;
> +	struct phy *phy = host->generic_phy;
> +	int ret = 0;
> +	u8 major;
> +	u16 minor, step;
> +	bool is_rate_B = (UFS_QCOM_LIMIT_HS_RATE == PA_HS_MODE_B)
> +							? true : false;
> +
> +	/* Assert PHY reset and apply PHY calibration values */
> +	ufs_qcom_assert_reset(hba);
> +	/* provide 1ms delay to let the reset pulse propagate */
> +	usleep_range(1000, 1100);
> +
> +	ufs_qcom_get_controller_revision(hba, &major, &minor, &step);
> +	ufs_qcom_phy_save_controller_version(phy, major, minor, step);
> +	ret = ufs_qcom_phy_calibrate_phy(phy, is_rate_B);
> +	if (ret) {
> +		dev_err(hba->dev, "%s: ufs_qcom_phy_calibrate_phy()
> failed, ret = %d\n",
> +			__func__, ret);
> +		goto out;
> +	}
> +
> +	/* De-assert PHY reset and start serdes */
> +	ufs_qcom_deassert_reset(hba);
> +
> +	/*
> +	 * after reset deassertion, phy will need all ref clocks,
> +	 * voltage, current to settle down before starting serdes.
> +	 */
> +	usleep_range(1000, 1100);
> +	ret = ufs_qcom_phy_start_serdes(phy);
> +	if (ret) {
> +		dev_err(hba->dev, "%s: ufs_qcom_phy_start_serdes() failed,
> ret = %d\n",
> +			__func__, ret);
> +		goto out;
> +	}
> +
> +	ret = ufs_qcom_phy_is_pcs_ready(phy);
> +	if (ret)
> +		dev_err(hba->dev, "%s: is_physical_coding_sublayer_ready()
> failed, ret = %d\n",
> +			__func__, ret);
> +
> +out:
> +	return ret;
> +}
> +
> +/*
> + * The UTP controller has a number of internal clock gating cells (CGCs).
> + * Internal hardware sub-modules within the UTP controller control the
> CGCs.
> + * Hardware CGCs disable the clock to inactivate UTP sub-modules not
> involved
> + * in a specific operation, UTP controller CGCs are by default disabled
> and
> + * this function enables them (after every UFS link startup) to save some
> power
> + * leakage.
> + */
> +static void ufs_qcom_enable_hw_clk_gating(struct ufs_hba *hba)
> +{
> +	ufshcd_writel(hba,
> +		ufshcd_readl(hba, REG_UFS_CFG2) | REG_UFS_CFG2_CGC_EN_ALL,
> +		REG_UFS_CFG2);
> +
> +	/* Ensure that HW clock gating is enabled before next operations
> */
> +	mb();
> +}
> +
> +static int ufs_qcom_hce_enable_notify(struct ufs_hba *hba, bool status)
> +{
> +	struct ufs_qcom_host *host = hba->priv;
> +	int err = 0;
> +
> +	switch (status) {
> +	case PRE_CHANGE:
> +		ufs_qcom_power_up_sequence(hba);
> +		/*
> +		 * The PHY PLL output is the source of tx/rx lane symbol
> +		 * clocks, hence, enable the lane clocks only after PHY
> +		 * is initialized.
> +		 */
> +		err = ufs_qcom_enable_lane_clks(host);
> +		break;
> +	case POST_CHANGE:
> +		/* check if UFS PHY moved from DISABLED to HIBERN8 */
> +		err = ufs_qcom_check_hibern8(hba);
> +		ufs_qcom_enable_hw_clk_gating(hba);
> +
> +		break;
> +	default:
> +		dev_err(hba->dev, "%s: invalid status %d\n", __func__,
> status);
> +		err = -EINVAL;
> +		break;
> +	}
> +	return err;
> +}
> +
> +/**
> + * Returns non-zero for success (which rate of core_clk) and 0
> + * in case of a failure
> + */
> +static unsigned long
> +ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear, u32 hs, u32 rate)
> +{
> +	struct ufs_clk_info *clki;
> +	u32 core_clk_period_in_ns;
> +	u32 tx_clk_cycles_per_us = 0;
> +	unsigned long core_clk_rate = 0;
> +	u32 core_clk_cycles_per_us = 0;
> +
> +	static u32 pwm_fr_table[][2] = {
> +		{UFS_PWM_G1, 0x1},
> +		{UFS_PWM_G2, 0x1},
> +		{UFS_PWM_G3, 0x1},
> +		{UFS_PWM_G4, 0x1},
> +	};
> +
> +	static u32 hs_fr_table_rA[][2] = {
> +		{UFS_HS_G1, 0x1F},
> +		{UFS_HS_G2, 0x3e},
> +	};
> +
> +	static u32 hs_fr_table_rB[][2] = {
> +		{UFS_HS_G1, 0x24},
> +		{UFS_HS_G2, 0x49},
> +	};
> +
> +	if (gear == 0) {
> +		dev_err(hba->dev, "%s: invalid gear = %d\n", __func__,
> gear);
> +		goto out_error;
> +	}
> +
> +	list_for_each_entry(clki, &hba->clk_list_head, list) {
> +		if (!strcmp(clki->name, "core_clk"))
> +			core_clk_rate = clk_get_rate(clki->clk);
> +	}
> +
> +	/* If frequency is smaller than 1MHz, set to 1MHz */
> +	if (core_clk_rate < DEFAULT_CLK_RATE_HZ)
> +		core_clk_rate = DEFAULT_CLK_RATE_HZ;
> +
> +	core_clk_cycles_per_us = core_clk_rate / USEC_PER_SEC;
> +	ufshcd_writel(hba, core_clk_cycles_per_us, REG_UFS_SYS1CLK_1US);
> +
> +	core_clk_period_in_ns = NSEC_PER_SEC / core_clk_rate;
> +	core_clk_period_in_ns <<= OFFSET_CLK_NS_REG;
> +	core_clk_period_in_ns &= MASK_CLK_NS_REG;
> +
> +	switch (hs) {
> +	case FASTAUTO_MODE:
> +	case FAST_MODE:
> +		if (rate == PA_HS_MODE_A) {
> +			if (gear > ARRAY_SIZE(hs_fr_table_rA)) {
> +				dev_err(hba->dev,
> +					"%s: index %d exceeds table size
> %zu\n",
> +					__func__, gear,
> +					ARRAY_SIZE(hs_fr_table_rA));
> +				goto out_error;
> +			}
> +			tx_clk_cycles_per_us = hs_fr_table_rA[gear-1][1];
> +		} else if (rate == PA_HS_MODE_B) {
> +			if (gear > ARRAY_SIZE(hs_fr_table_rB)) {
> +				dev_err(hba->dev,
> +					"%s: index %d exceeds table size
> %zu\n",
> +					__func__, gear,
> +					ARRAY_SIZE(hs_fr_table_rB));
> +				goto out_error;
> +			}
> +			tx_clk_cycles_per_us = hs_fr_table_rB[gear-1][1];
> +		} else {
> +			dev_err(hba->dev, "%s: invalid rate = %d\n",
> +				__func__, rate);
> +			goto out_error;
> +		}
> +		break;
> +	case SLOWAUTO_MODE:
> +	case SLOW_MODE:
> +		if (gear > ARRAY_SIZE(pwm_fr_table)) {
> +			dev_err(hba->dev,
> +					"%s: index %d exceeds table size
> %zu\n",
> +					__func__, gear,
> +					ARRAY_SIZE(pwm_fr_table));
> +			goto out_error;
> +		}
> +		tx_clk_cycles_per_us = pwm_fr_table[gear-1][1];
> +		break;
> +	case UNCHANGED:
> +	default:
> +		dev_err(hba->dev, "%s: invalid mode = %d\n", __func__,
> hs);
> +		goto out_error;
> +	}
> +
> +	/* this register 2 fields shall be written at once */
> +	ufshcd_writel(hba, core_clk_period_in_ns | tx_clk_cycles_per_us,
> +
> REG_UFS_TX_SYMBOL_CLK_NS_US);
> +	goto out;
> +
> +out_error:
> +	core_clk_rate = 0;
> +out:
> +	return core_clk_rate;
> +}
> +
> +static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, bool status)
> +{
> +	unsigned long core_clk_rate = 0;
> +	u32 core_clk_cycles_per_100ms;
> +
> +	switch (status) {
> +	case PRE_CHANGE:
> +		core_clk_rate = ufs_qcom_cfg_timers(hba, UFS_PWM_G1,
> +						    SLOWAUTO_MODE, 0);
> +		if (!core_clk_rate) {
> +			dev_err(hba->dev, "%s: ufs_qcom_cfg_timers()
> failed\n",
> +				__func__);
> +			return -EINVAL;
> +		}
> +		core_clk_cycles_per_100ms =
> +			(core_clk_rate / MSEC_PER_SEC) * 100;
> +		ufshcd_writel(hba, core_clk_cycles_per_100ms,
> +					REG_UFS_PA_LINK_STARTUP_TIMER);
> +		break;
> +	case POST_CHANGE:
> +		ufs_qcom_link_startup_post_change(hba);
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ufs_qcom_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
> +{
> +	struct ufs_qcom_host *host = hba->priv;
> +	struct phy *phy = host->generic_phy;
> +	int ret = 0;
> +
> +	if (ufs_qcom_is_link_off(hba)) {
> +		/*
> +		 * Disable the tx/rx lane symbol clocks before PHY is
> +		 * powered down as the PLL source should be disabled
> +		 * after downstream clocks are disabled.
> +		 */
> +		ufs_qcom_disable_lane_clks(host);
> +		phy_power_off(phy);
> +
> +		/* Assert PHY soft reset */
> +		ufs_qcom_assert_reset(hba);
> +		goto out;
> +	}
> +
> +	/*
> +	 * If UniPro link is not active, PHY ref_clk, main PHY analog
> power
> +	 * rail and low noise analog power rail for PLL can be switched
> off.
> +	 */
> +	if (!ufs_qcom_is_link_active(hba))
> +		phy_power_off(phy);
> +
> +out:
> +	return ret;
> +}
> +
> +static int ufs_qcom_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
> +{
> +	struct ufs_qcom_host *host = hba->priv;
> +	struct phy *phy = host->generic_phy;
> +	int err;
> +
> +	err = phy_power_on(phy);
> +	if (err) {
> +		dev_err(hba->dev, "%s: failed enabling regs, err = %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	hba->is_sys_suspended = false;
> +
> +out:
> +	return err;
> +}
> +
> +struct ufs_qcom_dev_params {
> +	u32 pwm_rx_gear;	/* pwm rx gear to work in */
> +	u32 pwm_tx_gear;	/* pwm tx gear to work in */
> +	u32 hs_rx_gear;		/* hs rx gear to work in */
> +	u32 hs_tx_gear;		/* hs tx gear to work in */
> +	u32 rx_lanes;		/* number of rx lanes */
> +	u32 tx_lanes;		/* number of tx lanes */
> +	u32 rx_pwr_pwm;		/* rx pwm working pwr */
> +	u32 tx_pwr_pwm;		/* tx pwm working pwr */
> +	u32 rx_pwr_hs;		/* rx hs working pwr */
> +	u32 tx_pwr_hs;		/* tx hs working pwr */
> +	u32 hs_rate;		/* rate A/B to work in HS */
> +	u32 desired_working_mode;
> +};
> +
> +static int ufs_qcom_get_pwr_dev_param(struct ufs_qcom_dev_params
> *qcom_param,
> +				      struct ufs_pa_layer_attr *dev_max,
> +				      struct ufs_pa_layer_attr
> *agreed_pwr)
> +{
> +	int min_qcom_gear;
> +	int min_dev_gear;
> +	bool is_dev_sup_hs = false;
> +	bool is_qcom_max_hs = false;
> +
> +	if (dev_max->pwr_rx == FAST_MODE)
> +		is_dev_sup_hs = true;
> +
> +	if (qcom_param->desired_working_mode == FAST) {
> +		is_qcom_max_hs = true;
> +		min_qcom_gear = min_t(u32, qcom_param->hs_rx_gear,
> +				      qcom_param->hs_tx_gear);
> +	} else {
> +		min_qcom_gear = min_t(u32, qcom_param->pwm_rx_gear,
> +				      qcom_param->pwm_tx_gear);
> +	}
> +
> +	/*
> +	 * device doesn't support HS but qcom_param->desired_working_mode
> is
> +	 * HS, thus device and qcom_param don't agree
> +	 */
> +	if (!is_dev_sup_hs && is_qcom_max_hs) {
> +		pr_err("%s: failed to agree on power mode (device doesn't
> support HS but requested power is HS)\n",
> +			__func__);
> +		return -ENOTSUPP;
> +	} else if (is_dev_sup_hs && is_qcom_max_hs) {
> +		/*
> +		 * since device supports HS, it supports FAST_MODE.
> +		 * since qcom_param->desired_working_mode is also HS
> +		 * then final decision (FAST/FASTAUTO) is done according
> +		 * to qcom_params as it is the restricting factor
> +		 */
> +		agreed_pwr->pwr_rx = agreed_pwr->pwr_tx =
> +						qcom_param->rx_pwr_hs;
> +	} else {
> +		/*
> +		 * here qcom_param->desired_working_mode is PWM.
> +		 * it doesn't matter whether device supports HS or PWM,
> +		 * in both cases qcom_param->desired_working_mode will
> +		 * determine the mode
> +		 */
> +		 agreed_pwr->pwr_rx = agreed_pwr->pwr_tx =
> +						qcom_param->rx_pwr_pwm;
> +	}
> +
> +	/*
> +	 * we would like tx to work in the minimum number of lanes
> +	 * between device capability and vendor preferences.
> +	 * the same decision will be made for rx
> +	 */
> +	agreed_pwr->lane_tx = min_t(u32, dev_max->lane_tx,
> +						qcom_param->tx_lanes);
> +	agreed_pwr->lane_rx = min_t(u32, dev_max->lane_rx,
> +						qcom_param->rx_lanes);
> +
> +	/* device maximum gear is the minimum between device rx and tx
> gears */
> +	min_dev_gear = min_t(u32, dev_max->gear_rx, dev_max->gear_tx);
> +
> +	/*
> +	 * if both device capabilities and vendor pre-defined preferences
> are
> +	 * both HS or both PWM then set the minimum gear to be the chosen
> +	 * working gear.
> +	 * if one is PWM and one is HS then the one that is PWM get to
> decide
> +	 * what is the gear, as it is the one that also decided previously
> what
> +	 * pwr the device will be configured to.
> +	 */
> +	if ((is_dev_sup_hs && is_qcom_max_hs) ||
> +	    (!is_dev_sup_hs && !is_qcom_max_hs))
> +		agreed_pwr->gear_rx = agreed_pwr->gear_tx =
> +			min_t(u32, min_dev_gear, min_qcom_gear);
> +	else if (!is_dev_sup_hs)
> +		agreed_pwr->gear_rx = agreed_pwr->gear_tx = min_dev_gear;
> +	else
> +		agreed_pwr->gear_rx = agreed_pwr->gear_tx = min_qcom_gear;
> +
> +	agreed_pwr->hs_rate = qcom_param->hs_rate;
> +	return 0;
> +}
> +
> +static int ufs_qcom_update_bus_bw_vote(struct ufs_qcom_host *host)
> +{
> +	int vote;
> +	int err = 0;
> +	char mode[BUS_VECTOR_NAME_LEN];
> +
> +	ufs_qcom_get_speed_mode(&host->dev_req_params, mode);
> +
> +	vote = ufs_qcom_get_bus_vote(host, mode);
> +	if (vote >= 0)
> +		err = ufs_qcom_set_bus_vote(host, vote);
> +	else
> +		err = vote;
> +
> +	if (err)
> +		dev_err(host->hba->dev, "%s: failed %d\n", __func__, err);
> +	else
> +		host->bus_vote.saved_vote = vote;
> +	return err;
> +}
> +
> +static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba,
> +				bool status,
> +				struct ufs_pa_layer_attr *dev_max_params,
> +				struct ufs_pa_layer_attr *dev_req_params)
> +{
> +	u32 val;
> +	struct ufs_qcom_host *host = hba->priv;
> +	struct phy *phy = host->generic_phy;
> +	struct ufs_qcom_dev_params ufs_qcom_cap;
> +	int ret = 0;
> +	int res = 0;
> +
> +	if (!dev_req_params) {
> +		pr_err("%s: incoming dev_req_params is NULL\n", __func__);
> +		ret = -EINVAL;
> +		goto out;
> +	}
> +
> +	switch (status) {
> +	case PRE_CHANGE:
> +		ufs_qcom_cap.tx_lanes = UFS_QCOM_LIMIT_NUM_LANES_TX;
> +		ufs_qcom_cap.rx_lanes = UFS_QCOM_LIMIT_NUM_LANES_RX;
> +		ufs_qcom_cap.hs_rx_gear = UFS_QCOM_LIMIT_HSGEAR_RX;
> +		ufs_qcom_cap.hs_tx_gear = UFS_QCOM_LIMIT_HSGEAR_TX;
> +		ufs_qcom_cap.pwm_rx_gear = UFS_QCOM_LIMIT_PWMGEAR_RX;
> +		ufs_qcom_cap.pwm_tx_gear = UFS_QCOM_LIMIT_PWMGEAR_TX;
> +		ufs_qcom_cap.rx_pwr_pwm = UFS_QCOM_LIMIT_RX_PWR_PWM;
> +		ufs_qcom_cap.tx_pwr_pwm = UFS_QCOM_LIMIT_TX_PWR_PWM;
> +		ufs_qcom_cap.rx_pwr_hs = UFS_QCOM_LIMIT_RX_PWR_HS;
> +		ufs_qcom_cap.tx_pwr_hs = UFS_QCOM_LIMIT_TX_PWR_HS;
> +		ufs_qcom_cap.hs_rate = UFS_QCOM_LIMIT_HS_RATE;
> +		ufs_qcom_cap.desired_working_mode =
> +					UFS_QCOM_LIMIT_DESIRED_MODE;
> +
> +		ret = ufs_qcom_get_pwr_dev_param(&ufs_qcom_cap,
> +						 dev_max_params,
> +						 dev_req_params);
> +		if (ret) {
> +			pr_err("%s: failed to determine capabilities\n",
> +					__func__);
> +			goto out;
> +		}
> +
> +		break;
> +	case POST_CHANGE:
> +		if (!ufs_qcom_cfg_timers(hba, dev_req_params->gear_rx,
> +					dev_req_params->pwr_rx,
> +					dev_req_params->hs_rate)) {
> +			dev_err(hba->dev, "%s: ufs_qcom_cfg_timers()
> failed\n",
> +				__func__);
> +			/*
> +			 * we return error code at the end of the routine,
> +			 * but continue to configure
> UFS_PHY_TX_LANE_ENABLE
> +			 * and bus voting as usual
> +			 */
> +			ret = -EINVAL;
> +		}
> +
> +		val = ~(MAX_U32 << dev_req_params->lane_tx);
> +		res = ufs_qcom_phy_set_tx_lane_enable(phy, val);
> +		if (res) {
> +			dev_err(hba->dev, "%s:
> ufs_qcom_phy_set_tx_lane_enable() failed res = %d\n",
> +				__func__, res);
> +			ret = res;
> +		}
> +
> +		/* cache the power mode parameters to use internally */
> +		memcpy(&host->dev_req_params,
> +				dev_req_params, sizeof(*dev_req_params));
> +		ufs_qcom_update_bus_bw_vote(host);
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		break;
> +	}
> +out:
> +	return ret;
> +}
> +
> +/**
> + * ufs_qcom_advertise_quirks - advertise the known QCOM UFS controller
> quirks
> + * @hba: host controller instance
> + *
> + * QCOM UFS host controller might have some non standard behaviours
> (quirks)
> + * than what is specified by UFSHCI specification. Advertise all such
> + * quirks to standard UFS host controller driver so standard takes them
> into
> + * account.
> + */
> +static void ufs_qcom_advertise_quirks(struct ufs_hba *hba)
> +{
> +	u8 major;
> +	u16 minor, step;
> +
> +	ufs_qcom_get_controller_revision(hba, &major, &minor, &step);
> +
> +	/*
> +	 * TBD
> +	 * here we should be advertising controller quirks according to
> +	 * controller version.
> +	 */
> +}
> +
> +static int ufs_qcom_get_bus_vote(struct ufs_qcom_host *host,
> +		const char *speed_mode)
> +{
> +	struct device *dev = host->hba->dev;
> +	struct device_node *np = dev->of_node;
> +	int err;
> +	const char *key = "qcom,bus-vector-names";
> +
> +	if (!speed_mode) {
> +		err = -EINVAL;
> +		goto out;
> +	}
> +
> +	if (host->bus_vote.is_max_bw_needed && !!strcmp(speed_mode,
> "MIN"))
> +		err = of_property_match_string(np, key, "MAX");
> +	else
> +		err = of_property_match_string(np, key, speed_mode);
> +
> +out:
> +	if (err < 0)
> +		dev_err(dev, "%s: Invalid %s mode %d\n",
> +				__func__, speed_mode, err);
> +	return err;
> +}
> +
> +static int ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote)
> +{
> +	int err = 0;
> +
> +	if (vote != host->bus_vote.curr_vote)
> +		host->bus_vote.curr_vote = vote;
> +
> +	return err;
> +}
> +
> +static void ufs_qcom_get_speed_mode(struct ufs_pa_layer_attr *p, char
> *result)
> +{
> +	int gear = max_t(u32, p->gear_rx, p->gear_tx);
> +	int lanes = max_t(u32, p->lane_rx, p->lane_tx);
> +	int pwr;
> +
> +	/* default to PWM Gear 1, Lane 1 if power mode is not initialized
> */
> +	if (!gear)
> +		gear = 1;
> +
> +	if (!lanes)
> +		lanes = 1;
> +
> +	if (!p->pwr_rx && !p->pwr_tx) {
> +		pwr = SLOWAUTO_MODE;
> +		snprintf(result, BUS_VECTOR_NAME_LEN, "MIN");
> +	} else if (p->pwr_rx == FAST_MODE || p->pwr_rx == FASTAUTO_MODE ||
> +		 p->pwr_tx == FAST_MODE || p->pwr_tx == FASTAUTO_MODE) {
> +		pwr = FAST_MODE;
> +		snprintf(result, BUS_VECTOR_NAME_LEN, "%s_R%s_G%d_L%d",
> "HS",
> +			 p->hs_rate == PA_HS_MODE_B ? "B" : "A", gear,
> lanes);
> +	} else {
> +		pwr = SLOW_MODE;
> +		snprintf(result, BUS_VECTOR_NAME_LEN, "%s_G%d_L%d",
> +			 "PWM", gear, lanes);
> +	}
> +}
> +
> +static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on)
> +{
> +	struct ufs_qcom_host *host = hba->priv;
> +	int err = 0;
> +	int vote = 0;
> +
> +	/*
> +	 * In case ufs_qcom_init() is not yet done, simply ignore.
> +	 * This ufs_qcom_setup_clocks() shall be called from
> +	 * ufs_qcom_init() after init is done.
> +	 */
> +	if (!host)
> +		return 0;
> +
> +	if (on) {
> +		err = ufs_qcom_phy_enable_iface_clk(host->generic_phy);
> +		if (err)
> +			goto out;
> +
> +		err = ufs_qcom_phy_enable_ref_clk(host->generic_phy);
> +		if (err) {
> +			dev_err(hba->dev, "%s enable phy ref clock failed,
> err=%d\n",
> +				__func__, err);
> +			ufs_qcom_phy_disable_iface_clk(host->generic_phy);
> +			goto out;
> +		}
> +		/* enable the device ref clock */
> +		ufs_qcom_phy_enable_dev_ref_clk(host->generic_phy);
> +		vote = host->bus_vote.saved_vote;
> +		if (vote == host->bus_vote.min_bw_vote)
> +			ufs_qcom_update_bus_bw_vote(host);
> +	} else {
> +		/* M-PHY RMMI interface clocks can be turned off */
> +		ufs_qcom_phy_disable_iface_clk(host->generic_phy);
> +		if (!ufs_qcom_is_link_active(hba)) {
> +			/* turn off UFS local PHY ref_clk */
> +			ufs_qcom_phy_disable_ref_clk(host->generic_phy);
> +			/* disable device ref_clk */
> +
> ufs_qcom_phy_disable_dev_ref_clk(host->generic_phy);
> +		}
> +		vote = host->bus_vote.min_bw_vote;
> +	}
> +
> +	err = ufs_qcom_set_bus_vote(host, vote);
> +	if (err)
> +		dev_err(hba->dev, "%s: set bus vote failed %d\n",
> +				__func__, err);
> +
> +out:
> +	return err;
> +}
> +
> +static ssize_t
> +show_ufs_to_mem_max_bus_bw(struct device *dev, struct device_attribute
> *attr,
> +			char *buf)
> +{
> +	struct ufs_hba *hba = dev_get_drvdata(dev);
> +	struct ufs_qcom_host *host = hba->priv;
> +
> +	return snprintf(buf, PAGE_SIZE, "%u\n",
> +			host->bus_vote.is_max_bw_needed);
> +}
> +
> +static ssize_t
> +store_ufs_to_mem_max_bus_bw(struct device *dev, struct device_attribute
> *attr,
> +		const char *buf, size_t count)
> +{
> +	struct ufs_hba *hba = dev_get_drvdata(dev);
> +	struct ufs_qcom_host *host = hba->priv;
> +	uint32_t value;
> +
> +	if (!kstrtou32(buf, 0, &value)) {
> +		host->bus_vote.is_max_bw_needed = !!value;
> +		ufs_qcom_update_bus_bw_vote(host);
> +	}
> +
> +	return count;
> +}
> +
> +static int ufs_qcom_bus_register(struct ufs_qcom_host *host)
> +{
> +	int err;
> +	struct device *dev = host->hba->dev;
> +	struct device_node *np = dev->of_node;
> +
> +	err = of_property_count_strings(np, "qcom,bus-vector-names");
> +	if (err < 0 ) {
> +		dev_err(dev, "%s: qcom,bus-vector-names not specified
> correctly %d\n",
> +				__func__, err);
> +		goto out;
> +	}
> +
> +	/* cache the vote index for minimum and maximum bandwidth */
> +	host->bus_vote.min_bw_vote = ufs_qcom_get_bus_vote(host, "MIN");
> +	host->bus_vote.max_bw_vote = ufs_qcom_get_bus_vote(host, "MAX");
> +
> +	host->bus_vote.max_bus_bw.show = show_ufs_to_mem_max_bus_bw;
> +	host->bus_vote.max_bus_bw.store = store_ufs_to_mem_max_bus_bw;
> +	sysfs_attr_init(&host->bus_vote.max_bus_bw.attr);
> +	host->bus_vote.max_bus_bw.attr.name = "max_bus_bw";
> +	host->bus_vote.max_bus_bw.attr.mode = S_IRUGO | S_IWUSR;
> +	err = device_create_file(dev, &host->bus_vote.max_bus_bw);
> +out:
> +	return err;
> +}
> +
> +#define	ANDROID_BOOT_DEV_MAX	30
> +static char android_boot_dev[ANDROID_BOOT_DEV_MAX];
> +static int get_android_boot_dev(char *str)
> +{
> +	strlcpy(android_boot_dev, str, ANDROID_BOOT_DEV_MAX);
> +	return 1;
> +}
> +__setup("androidboot.bootdevice=", get_android_boot_dev);
> +
> +/**
> + * ufs_qcom_init - bind phy with controller
> + * @hba: host controller instance
> + *
> + * Binds PHY with controller and powers up PHY enabling clocks
> + * and regulators.
> + *
> + * Returns -EPROBE_DEFER if binding fails, returns negative error
> + * on phy power up failure and returns zero on success.
> + */
> +static int ufs_qcom_init(struct ufs_hba *hba)
> +{
> +	int err;
> +	struct device *dev = hba->dev;
> +	struct ufs_qcom_host *host;
> +
> +	if (strlen(android_boot_dev) && strcmp(android_boot_dev,
> dev_name(dev)))
> +		return -ENODEV;
> +
> +	host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
> +	if (!host) {
> +		err = -ENOMEM;
> +		dev_err(dev, "%s: no memory for qcom ufs host\n",
> __func__);
> +		goto out;
> +	}
> +
> +	host->hba = hba;
> +	hba->priv = (void *)host;
> +
> +	host->generic_phy = devm_phy_get(dev, "ufsphy");
> +
> +	if (IS_ERR(host->generic_phy)) {
> +		err = PTR_ERR(host->generic_phy);
> +		dev_err(dev, "%s: PHY get failed %d\n", __func__, err);
> +		goto out;
> +	}
> +
> +	err = ufs_qcom_bus_register(host);
> +	if (err)
> +		goto out_host_free;
> +
> +	phy_init(host->generic_phy);
> +	err = phy_power_on(host->generic_phy);
> +	if (err)
> +		goto out_unregister_bus;
> +
> +	err = ufs_qcom_init_lane_clks(host);
> +	if (err)
> +		goto out_disable_phy;
> +
> +	ufs_qcom_advertise_quirks(hba);
> +
> +	hba->caps |= UFSHCD_CAP_CLK_GATING | UFSHCD_CAP_CLK_SCALING;
> +	hba->caps |= UFSHCD_CAP_AUTO_BKOPS_SUSPEND;
> +
> +	ufs_qcom_setup_clocks(hba, true);
> +
> +	if (hba->dev->id < MAX_UFS_QCOM_HOSTS)
> +		ufs_qcom_hosts[hba->dev->id] = host;
> +
> +	goto out;
> +
> +out_disable_phy:
> +	phy_power_off(host->generic_phy);
> +out_unregister_bus:
> +	phy_exit(host->generic_phy);
> +out_host_free:
> +	devm_kfree(dev, host);
> +	hba->priv = NULL;
> +out:
> +	return err;
> +}
> +
> +static void ufs_qcom_exit(struct ufs_hba *hba)
> +{
> +	struct ufs_qcom_host *host = hba->priv;
> +
> +	ufs_qcom_disable_lane_clks(host);
> +	phy_power_off(host->generic_phy);
> +}
> +
> +static
> +void ufs_qcom_clk_scale_notify(struct ufs_hba *hba)
> +{
> +	struct ufs_qcom_host *host = hba->priv;
> +	struct ufs_pa_layer_attr *dev_req_params = &host->dev_req_params;
> +
> +	if (!dev_req_params)
> +		return;
> +
> +	ufs_qcom_cfg_timers(hba, dev_req_params->gear_rx,
> +				dev_req_params->pwr_rx,
> +				dev_req_params->hs_rate);
> +}
> +
> +/**
> + * struct ufs_hba_qcom_vops - UFS QCOM specific variant operations
> + *
> + * The variant operations configure the necessary controller and PHY
> + * handshake during initialization.
> + */
> +static const struct ufs_hba_variant_ops ufs_hba_qcom_vops = {
> +	.name                   = "qcom",
> +	.init                   = ufs_qcom_init,
> +	.exit                   = ufs_qcom_exit,
> +	.clk_scale_notify	= ufs_qcom_clk_scale_notify,
> +	.setup_clocks           = ufs_qcom_setup_clocks,
> +	.hce_enable_notify      = ufs_qcom_hce_enable_notify,
> +	.link_startup_notify    = ufs_qcom_link_startup_notify,
> +	.pwr_change_notify	= ufs_qcom_pwr_change_notify,
> +	.suspend		= ufs_qcom_suspend,
> +	.resume			= ufs_qcom_resume,
> +};
> +EXPORT_SYMBOL(ufs_hba_qcom_vops);
> diff --git a/drivers/scsi/ufs/ufs-qcom.h b/drivers/scsi/ufs/ufs-qcom.h
> new file mode 100644
> index 0000000..9a6febd
> --- /dev/null
> +++ b/drivers/scsi/ufs/ufs-qcom.h
> @@ -0,0 +1,170 @@
> +/* Copyright (c) 2013-2015, 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.
> + *
> + */
> +
> +#ifndef UFS_QCOM_H_
> +#define UFS_QCOM_H_
> +
> +#define MAX_UFS_QCOM_HOSTS	1
> +#define MAX_U32                 (~(u32)0)
> +#define MPHY_TX_FSM_STATE       0x41
> +#define TX_FSM_HIBERN8          0x1
> +#define HBRN8_POLL_TOUT_MS      100
> +#define DEFAULT_CLK_RATE_HZ     1000000
> +#define BUS_VECTOR_NAME_LEN     32
> +
> +#define UFS_HW_VER_MAJOR_SHFT	(28)
> +#define UFS_HW_VER_MAJOR_MASK	(0x000F << UFS_HW_VER_MAJOR_SHFT)
> +#define UFS_HW_VER_MINOR_SHFT	(16)
> +#define UFS_HW_VER_MINOR_MASK	(0x0FFF << UFS_HW_VER_MINOR_SHFT)
> +#define UFS_HW_VER_STEP_SHFT	(0)
> +#define UFS_HW_VER_STEP_MASK	(0xFFFF << UFS_HW_VER_STEP_SHFT)
> +
> +/* vendor specific pre-defined parameters */
> +#define SLOW 1
> +#define FAST 2
> +
> +#define UFS_QCOM_LIMIT_NUM_LANES_RX	2
> +#define UFS_QCOM_LIMIT_NUM_LANES_TX	2
> +#define UFS_QCOM_LIMIT_HSGEAR_RX	UFS_HS_G2
> +#define UFS_QCOM_LIMIT_HSGEAR_TX	UFS_HS_G2
> +#define UFS_QCOM_LIMIT_PWMGEAR_RX	UFS_PWM_G4
> +#define UFS_QCOM_LIMIT_PWMGEAR_TX	UFS_PWM_G4
> +#define UFS_QCOM_LIMIT_RX_PWR_PWM	SLOW_MODE
> +#define UFS_QCOM_LIMIT_TX_PWR_PWM	SLOW_MODE
> +#define UFS_QCOM_LIMIT_RX_PWR_HS	FAST_MODE
> +#define UFS_QCOM_LIMIT_TX_PWR_HS	FAST_MODE
> +#define UFS_QCOM_LIMIT_HS_RATE		PA_HS_MODE_B
> +#define UFS_QCOM_LIMIT_DESIRED_MODE	FAST
> +
> +/* QCOM UFS host controller vendor specific registers */
> +enum {
> +	REG_UFS_SYS1CLK_1US                 = 0xC0,
> +	REG_UFS_TX_SYMBOL_CLK_NS_US         = 0xC4,
> +	REG_UFS_LOCAL_PORT_ID_REG           = 0xC8,
> +	REG_UFS_PA_ERR_CODE                 = 0xCC,
> +	REG_UFS_RETRY_TIMER_REG             = 0xD0,
> +	REG_UFS_PA_LINK_STARTUP_TIMER       = 0xD8,
> +	REG_UFS_CFG1                        = 0xDC,
> +	REG_UFS_CFG2                        = 0xE0,
> +	REG_UFS_HW_VERSION                  = 0xE4,
> +
> +	UFS_DBG_RD_REG_UAWM			= 0x100,
> +	UFS_DBG_RD_REG_UARM			= 0x200,
> +	UFS_DBG_RD_REG_TXUC			= 0x300,
> +	UFS_DBG_RD_REG_RXUC			= 0x400,
> +	UFS_DBG_RD_REG_DFC			= 0x500,
> +	UFS_DBG_RD_REG_TRLUT			= 0x600,
> +	UFS_DBG_RD_REG_TMRLUT			= 0x700,
> +	UFS_UFS_DBG_RD_REG_OCSC			= 0x800,
> +
> +	UFS_UFS_DBG_RD_DESC_RAM			= 0x1500,
> +	UFS_UFS_DBG_RD_PRDT_RAM			= 0x1700,
> +	UFS_UFS_DBG_RD_RESP_RAM			= 0x1800,
> +	UFS_UFS_DBG_RD_EDTL_RAM			= 0x1900,
> +};
> +
> +/* bit definitions for REG_UFS_CFG2 register */
> +#define UAWM_HW_CGC_EN		(1 << 0)
> +#define UARM_HW_CGC_EN		(1 << 1)
> +#define TXUC_HW_CGC_EN		(1 << 2)
> +#define RXUC_HW_CGC_EN		(1 << 3)
> +#define DFC_HW_CGC_EN		(1 << 4)
> +#define TRLUT_HW_CGC_EN		(1 << 5)
> +#define TMRLUT_HW_CGC_EN	(1 << 6)
> +#define OCSC_HW_CGC_EN		(1 << 7)
> +
> +#define REG_UFS_CFG2_CGC_EN_ALL (UAWM_HW_CGC_EN | UARM_HW_CGC_EN |\
> +				 TXUC_HW_CGC_EN | RXUC_HW_CGC_EN |\
> +				 DFC_HW_CGC_EN | TRLUT_HW_CGC_EN |\
> +				 TMRLUT_HW_CGC_EN | OCSC_HW_CGC_EN)
> +
> +/* bit offset */
> +enum {
> +	OFFSET_UFS_PHY_SOFT_RESET           = 1,
> +	OFFSET_CLK_NS_REG                   = 10,
> +};
> +
> +/* bit masks */
> +enum {
> +	MASK_UFS_PHY_SOFT_RESET             = 0x2,
> +	MASK_TX_SYMBOL_CLK_1US_REG          = 0x3FF,
> +	MASK_CLK_NS_REG                     = 0xFFFC00,
> +};
> +
> +enum ufs_qcom_phy_init_type {
> +	UFS_PHY_INIT_FULL,
> +	UFS_PHY_INIT_CFG_RESTORE,
> +};
> +
> +static inline void
> +ufs_qcom_get_controller_revision(struct ufs_hba *hba,
> +				 u8 *major, u16 *minor, u16 *step)
> +{
> +	u32 ver = ufshcd_readl(hba, REG_UFS_HW_VERSION);
> +
> +	*major = (ver & UFS_HW_VER_MAJOR_MASK) >> UFS_HW_VER_MAJOR_SHFT;
> +	*minor = (ver & UFS_HW_VER_MINOR_MASK) >> UFS_HW_VER_MINOR_SHFT;
> +	*step = (ver & UFS_HW_VER_STEP_MASK) >> UFS_HW_VER_STEP_SHFT;
> +};
> +
> +static inline void ufs_qcom_assert_reset(struct ufs_hba *hba)
> +{
> +	ufshcd_rmwl(hba, MASK_UFS_PHY_SOFT_RESET,
> +			1 << OFFSET_UFS_PHY_SOFT_RESET, REG_UFS_CFG1);
> +
> +	/*
> +	 * Make sure assertion of ufs phy reset is written to
> +	 * register before returning
> +	 */
> +	mb();
> +}
> +
> +static inline void ufs_qcom_deassert_reset(struct ufs_hba *hba)
> +{
> +	ufshcd_rmwl(hba, MASK_UFS_PHY_SOFT_RESET,
> +			0 << OFFSET_UFS_PHY_SOFT_RESET, REG_UFS_CFG1);
> +
> +	/*
> +	 * Make sure de-assertion of ufs phy reset is written to
> +	 * register before returning
> +	 */
> +	mb();
> +}
> +
> +struct ufs_qcom_bus_vote {
> +	uint32_t client_handle;
> +	uint32_t curr_vote;
> +	int min_bw_vote;
> +	int max_bw_vote;
> +	int saved_vote;
> +	bool is_max_bw_needed;
> +	struct device_attribute max_bus_bw;
> +};
> +
> +struct ufs_qcom_host {
> +	struct phy *generic_phy;
> +	struct ufs_hba *hba;
> +	struct ufs_qcom_bus_vote bus_vote;
> +	struct ufs_pa_layer_attr dev_req_params;
> +	struct clk *rx_l0_sync_clk;
> +	struct clk *tx_l0_sync_clk;
> +	struct clk *rx_l1_sync_clk;
> +	struct clk *tx_l1_sync_clk;
> +	bool is_lane_clks_enabled;
> +};
> +
> +#define ufs_qcom_is_link_off(hba) ufshcd_is_link_off(hba)
> +#define ufs_qcom_is_link_active(hba) ufshcd_is_link_active(hba)
> +#define ufs_qcom_is_link_hibern8(hba) ufshcd_is_link_hibern8(hba)
> +
> +#endif /* UFS_QCOM_H_ */
> --
> 1.8.5.2
>
> --
> QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
> of Code Aurora Forum, hosted by The Linux Foundation
> --
> To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v7 5/5] scsi: ufs-qcom-ice: add Inline Crypto Engine (ICE) support for UFS
  2015-01-15 14:32 ` [PATCH v7 5/5] scsi: ufs-qcom-ice: add Inline Crypto Engine (ICE) support for UFS Yaniv Gardi
@ 2015-01-15 15:21     ` Dov Levenglick
  2015-01-23  8:48   ` Paul Bolle
  1 sibling, 0 replies; 27+ messages in thread
From: Dov Levenglick @ 2015-01-15 15:21 UTC (permalink / raw)
  Cc: james.bottomley, hch, linux-kernel, linux-scsi, linux-arm-msm,
	santoshsy, linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Yaniv Gardi, Vinayak Holikatti, James E.J. Bottomley

Reviewed-by: Dov Levenglick <dovl@codeaurora.org>

> From: Yaniv Gardi <ygardi@qti.qualcomm.com>
>
> In-order to enhance storage encryption performance,
> an Inline Cryptographic Engine is introduced to UFS.
> This patch adds in-line encryption capabilities to the UFS
> driver.
>
> Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>
>
> ---
>  drivers/scsi/ufs/Kconfig        |  12 +
>  drivers/scsi/ufs/Makefile       |   1 +
>  drivers/scsi/ufs/ufs-qcom-ice.c | 520
> ++++++++++++++++++++++++++++++++++++++++
>  drivers/scsi/ufs/ufs-qcom-ice.h | 113 +++++++++
>  drivers/scsi/ufs/ufs-qcom.c     |  56 ++++-
>  drivers/scsi/ufs/ufs-qcom.h     |  25 ++
>  6 files changed, 726 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/scsi/ufs/ufs-qcom-ice.c
>  create mode 100644 drivers/scsi/ufs/ufs-qcom-ice.h
>
> diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig
> index 8a1f4b3..ecf34ed 100644
> --- a/drivers/scsi/ufs/Kconfig
> +++ b/drivers/scsi/ufs/Kconfig
> @@ -83,3 +83,15 @@ config SCSI_UFS_QCOM
>
>  	  Select this if you have UFS controller on QCOM chipset.
>  	  If unsure, say N.
> +
> +config SCSI_UFS_QCOM_ICE
> +	bool "QCOM specific hooks to Inline Crypto Engine for UFS driver"
> +	depends on SCSI_UFS_QCOM && CRYPTO_DEV_QCOM_ICE
> +	help
> +	  This selects the QCOM specific additions to support Inline
> Crypto
> +	  Engine (ICE).
> +	  ICE accelerates the crypto operations and maintains the high UFS
> +	  performance.
> +
> +	  Select this if you have ICE supported for UFS on QCOM chipset.
> +	  If unsure, say N.
> diff --git a/drivers/scsi/ufs/Makefile b/drivers/scsi/ufs/Makefile
> index 8303bcc..31adca5 100644
> --- a/drivers/scsi/ufs/Makefile
> +++ b/drivers/scsi/ufs/Makefile
> @@ -1,5 +1,6 @@
>  # UFSHCD makefile
>  obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-qcom.o
> +obj-$(CONFIG_SCSI_UFS_QCOM_ICE) += ufs-qcom-ice.o
>  obj-$(CONFIG_SCSI_UFSHCD) += ufshcd.o
>  obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o
>  obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o
> diff --git a/drivers/scsi/ufs/ufs-qcom-ice.c
> b/drivers/scsi/ufs/ufs-qcom-ice.c
> new file mode 100644
> index 0000000..9202b73
> --- /dev/null
> +++ b/drivers/scsi/ufs/ufs-qcom-ice.c
> @@ -0,0 +1,520 @@
> +/* Copyright (c) 2014-2015, 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/of.h>
> +#include <linux/async.h>
> +#include <linux/blkdev.h>
> +#include <crypto/ice.h>
> +
> +#include "ufs-qcom-ice.h"
> +#include "ufshcd.h"
> +
> +#define UFS_QCOM_CRYPTO_LABEL "ufs-qcom-crypto"
> +/* Timeout waiting for ICE initialization, that requires TZ access */
> +#define UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS 500
> +
> +static void ufs_qcom_ice_success_cb(void *host_ctrl,
> +				enum ice_event_completion evt)
> +{
> +	struct ufs_qcom_host *qcom_host = (struct ufs_qcom_host
> *)host_ctrl;
> +
> +	if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_DISABLED &&
> +	    evt == ICE_INIT_COMPLETION)
> +		qcom_host->ice.state = UFS_QCOM_ICE_STATE_ACTIVE;
> +	 else if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_SUSPENDED &&
> +		   evt == ICE_RESUME_COMPLETION)
> +		qcom_host->ice.state = UFS_QCOM_ICE_STATE_ACTIVE;
> +
> +	complete(&qcom_host->ice.async_done);
> +}
> +
> +static void ufs_qcom_ice_error_cb(void *host_ctrl, enum ice_error_code
> evt)
> +{
> +	struct ufs_qcom_host *qcom_host = (struct ufs_qcom_host
> *)host_ctrl;
> +
> +	dev_err(qcom_host->hba->dev, "%s: Error in ice operation %d",
> +		__func__, evt);
> +
> +	if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_ACTIVE)
> +		qcom_host->ice.state = UFS_QCOM_ICE_STATE_DISABLED;
> +
> +	complete(&qcom_host->ice.async_done);
> +}
> +
> +static struct platform_device *ufs_qcom_ice_get_pdevice(struct device
> *ufs_dev)
> +{
> +	struct device_node *node;
> +	struct platform_device *ice_pdev = NULL;
> +
> +	node = of_parse_phandle(ufs_dev->of_node, UFS_QCOM_CRYPTO_LABEL,
> 0);
> +
> +	if (!node) {
> +		dev_err(ufs_dev, "%s: ufs-qcom-crypto property not
> specified\n",
> +			__func__);
> +		goto out;
> +	}
> +
> +	ice_pdev = qcom_ice_get_pdevice(node);
> +out:
> +	return ice_pdev;
> +}
> +
> +static
> +struct qcom_ice_variant_ops *ufs_qcom_ice_get_vops(struct device
> *ufs_dev)
> +{
> +	struct qcom_ice_variant_ops *ice_vops = NULL;
> +	struct device_node *node;
> +
> +	node = of_parse_phandle(ufs_dev->of_node, UFS_QCOM_CRYPTO_LABEL,
> 0);
> +
> +	if (!node) {
> +		dev_err(ufs_dev, "%s: ufs-qcom-crypto property not
> specified\n",
> +			__func__);
> +		goto out;
> +	}
> +
> +	ice_vops = qcom_ice_get_variant_ops(node);
> +
> +	if (!ice_vops)
> +		dev_err(ufs_dev, "%s: invalid ice_vops\n", __func__);
> +
> +	of_node_put(node);
> +out:
> +	return ice_vops;
> +}
> +
> +/**
> + * ufs_qcom_ice_get_dev() - sets pointers to ICE data structs in UFS QCom
> host
> + * @qcom_host:	Pointer to a UFS QCom internal host structure.
> + *
> + * Sets ICE platform device pointer and ICE vops structure
> + * corresponding to the current UFS device.
> + *
> + * Return: -EINVAL in-case of invalid input parameters:
> + *  qcom_host, qcom_host->hba or qcom_host->hba->dev
> + *         -ENODEV in-case ICE device is not required
> + *         -EPROBE_DEFER in-case ICE is required and hasn't been probed
> yet
> + *         0 otherwise
> + */
> +int ufs_qcom_ice_get_dev(struct ufs_qcom_host *qcom_host)
> +{
> +	struct device *ufs_dev;
> +	int err = 0;
> +
> +	if (!qcom_host || !qcom_host->hba || !qcom_host->hba->dev) {
> +		pr_err("%s: invalid qcom_host %p or qcom_host->hba or
> qcom_host->hba->dev\n",
> +			__func__, qcom_host);
> +		err = -EINVAL;
> +		goto out;
> +	}
> +
> +	ufs_dev = qcom_host->hba->dev;
> +
> +	qcom_host->ice.vops  = ufs_qcom_ice_get_vops(ufs_dev);
> +	qcom_host->ice.pdev = ufs_qcom_ice_get_pdevice(ufs_dev);
> +
> +	if (qcom_host->ice.pdev == ERR_PTR(-EPROBE_DEFER)) {
> +		dev_err(ufs_dev, "%s: ICE device not probed yet\n",
> +			__func__);
> +		qcom_host->ice.pdev = NULL;
> +		qcom_host->ice.vops = NULL;
> +		err = -EPROBE_DEFER;
> +		goto out;
> +	}
> +
> +	if (!qcom_host->ice.pdev || !qcom_host->ice.vops) {
> +		dev_err(ufs_dev, "%s: invalid platform device %p or vops
> %p\n",
> +			__func__, qcom_host->ice.pdev,
> qcom_host->ice.vops);
> +		qcom_host->ice.pdev = NULL;
> +		qcom_host->ice.vops = NULL;
> +		err = -ENODEV;
> +		goto out;
> +	}
> +
> +	qcom_host->ice.state = UFS_QCOM_ICE_STATE_DISABLED;
> +
> +out:
> +	return err;
> +
> +}
> +
> +/**
> + * ufs_qcom_ice_init() - initializes the ICE-UFS interface and ICE device
> + * @qcom_host:	Pointer to a UFS QCom internal host structure.
> + *		qcom_host, qcom_host->hba and qcom_host->hba->dev should
> all
> + *		be valid pointers.
> + *
> + * Return: -EINVAL in-case of an error
> + *         0 otherwise
> + */
> +int ufs_qcom_ice_init(struct ufs_qcom_host *qcom_host)
> +{
> +	struct device *ufs_dev = qcom_host->hba->dev;
> +	int err = -EINVAL;
> +
> +	init_completion(&qcom_host->ice.async_done);
> +	err = qcom_host->ice.vops->init(qcom_host->ice.pdev,
> +				qcom_host,
> +				ufs_qcom_ice_success_cb,
> +				ufs_qcom_ice_error_cb);
> +	if (err) {
> +		dev_err(ufs_dev, "%s: ice init failed. err = %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	if (!wait_for_completion_timeout(&qcom_host->ice.async_done,
> +
> msecs_to_jiffies(UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS))) {
> +		dev_err(qcom_host->hba->dev,
> +			"%s: error. got timeout after %d ms\n",
> +			__func__, UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS);
> +		err = -ETIMEDOUT;
> +		goto out;
> +	}
> +
> +	if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) {
> +		dev_err(qcom_host->hba->dev,
> +			"%s: error. ice.state (%d) is not in active
> state\n",
> +			__func__, qcom_host->ice.state);
> +		err = -EINVAL;
> +	}
> +
> +out:
> +	return err;
> +}
> +
> +static inline bool ufs_qcom_is_data_cmd(char cmd_op, bool is_write)
> +{
> +	if (is_write) {
> +		if (cmd_op == WRITE_6 || cmd_op == WRITE_10 ||
> +		    cmd_op == WRITE_16)
> +			return true;
> +	} else {
> +		if (cmd_op == READ_6 || cmd_op == READ_10 ||
> +		    cmd_op == READ_16)
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +/**
> + * ufs_qcom_ice_cfg() - configures UFS's ICE registers for an ICE
> transaction
> + * @qcom_host:	Pointer to a UFS QCom internal host structure.
> + *		qcom_host, qcom_host->hba and qcom_host->hba->dev should
> all
> + *		be valid pointers.
> + * @cmd:	Pointer to a valid scsi command. cmd->request should also
> be
> + *              a valid pointer.
> + *
> + * Return: -EINVAL in-case of an error
> + *         0 otherwise
> + */
> +int ufs_qcom_ice_cfg(struct ufs_qcom_host *qcom_host, struct scsi_cmnd
> *cmd)
> +{
> +	struct device *dev = qcom_host->hba->dev;
> +	int err = 0;
> +	struct ice_data_setting ice_set;
> +	unsigned int slot = 0;
> +	sector_t lba = 0;
> +	unsigned int ctrl_info_2_val = 0;
> +	unsigned int bypass = 0;
> +	struct request *req;
> +	char cmd_op;
> +
> +	if (!qcom_host->ice.pdev || !qcom_host->ice.vops) {
> +		dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
> +		goto out;
> +	}
> +
> +	if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) {
> +		dev_err(dev, "%s: ice state (%d) is not active\n",
> +			__func__, qcom_host->ice.state);
> +		return -EINVAL;
> +	}
> +
> +	req = cmd->request;
> +	if (req->bio)
> +		lba = req->bio->bi_sector;
> +
> +	slot = req->tag;
> +	if (slot < 0 || slot > qcom_host->hba->nutrs) {
> +		dev_err(dev, "%s: slot (%d) is out of boundaries
> (0...%d)\n",
> +			__func__, slot, qcom_host->hba->nutrs);
> +		return -EINVAL;
> +	}
> +
> +	memset(&ice_set, sizeof(ice_set), 0);
> +	if (qcom_host->ice.vops->config) {
> +		err = qcom_host->ice.vops->config(qcom_host->ice.pdev,
> +							req, &ice_set);
> +
> +		if (err) {
> +			dev_err(dev, "%s: error in ice_vops->config %d\n",
> +				__func__, err);
> +			goto out;
> +		}
> +	}
> +
> +	cmd_op = cmd->cmnd[0];
> +
> +#define UFS_QCOM_DIR_WRITE	true
> +#define UFS_QCOM_DIR_READ	false
> +	/* if non data command, bypass shall be enabled */
> +	if (!ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_WRITE) &&
> +	    !ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_READ))
> +		bypass = UFS_QCOM_ICE_ENABLE_BYPASS;
> +	/* if writing data command */
> +	else if (ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_WRITE))
> +		bypass = ice_set.encr_bypass ? UFS_QCOM_ICE_ENABLE_BYPASS
> :
> +
> UFS_QCOM_ICE_DISABLE_BYPASS;
> +	/* if reading data command */
> +	else if (ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_READ))
> +		bypass = ice_set.decr_bypass ? UFS_QCOM_ICE_ENABLE_BYPASS
> :
> +
> UFS_QCOM_ICE_DISABLE_BYPASS;
> +
> +	/* Configure ICE index */
> +	ctrl_info_2_val =
> +		(ice_set.crypto_data.key_index &
> +		 MASK_UFS_QCOM_ICE_CTRL_INFO_2_KEY_INDEX)
> +		 << OFFSET_UFS_QCOM_ICE_CTRL_INFO_2_KEY_INDEX;
> +
> +	/* Configure data unit size of transfer request */
> +	ctrl_info_2_val |=
> +		(UFS_QCOM_ICE_TR_DATA_UNIT_4_KB &
> +		 MASK_UFS_QCOM_ICE_CTRL_INFO_2_CDU)
> +		 << OFFSET_UFS_QCOM_ICE_CTRL_INFO_2_CDU;
> +
> +	/* Configure ICE bypass mode */
> +	ctrl_info_2_val |=
> +		(bypass & MASK_UFS_QCOM_ICE_CTRL_INFO_2_BYPASS)
> +		 << OFFSET_UFS_QCOM_ICE_CTRL_INFO_2_BYPASS;
> +
> +	ufshcd_writel(qcom_host->hba, lba,
> +		     (REG_UFS_QCOM_ICE_CTRL_INFO_1_n + 8 * slot));
> +
> +	ufshcd_writel(qcom_host->hba, ctrl_info_2_val,
> +		     (REG_UFS_QCOM_ICE_CTRL_INFO_2_n + 8 * slot));
> +
> +	/*
> +	 * Ensure UFS-ICE registers are being configured
> +	 * before next operation, otherwise UFS Host Controller might
> +	 * set get errors
> +	 */
> +	mb();
> +out:
> +	return err;
> +}
> +
> +/**
> + * ufs_qcom_ice_reset() - resets UFS-ICE interface and ICE device
> + * @qcom_host:	Pointer to a UFS QCom internal host structure.
> + *		qcom_host, qcom_host->hba and qcom_host->hba->dev should
> all
> + *		be valid pointers.
> + *
> + * Return: -EINVAL in-case of an error
> + *         0 otherwise
> + */
> +int ufs_qcom_ice_reset(struct ufs_qcom_host *qcom_host)
> +{
> +	struct device *dev = qcom_host->hba->dev;
> +	int err = 0;
> +
> +	if (!qcom_host->ice.pdev) {
> +		dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
> +		goto out;
> +	}
> +
> +	if (!qcom_host->ice.vops) {
> +		dev_err(dev, "%s: invalid ice_vops\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE)
> +		goto out;
> +
> +	init_completion(&qcom_host->ice.async_done);
> +
> +	if (qcom_host->ice.vops->reset) {
> +		err = qcom_host->ice.vops->reset(qcom_host->ice.pdev);
> +		if (err) {
> +			dev_err(dev, "%s: ice_vops->reset failed. err
> %d\n",
> +				__func__, err);
> +			goto out;
> +		}
> +	}
> +
> +	if (!wait_for_completion_timeout(&qcom_host->ice.async_done,
> +	     msecs_to_jiffies(UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS))) {
> +		dev_err(dev,
> +			"%s: error. got timeout after %d ms\n",
> +			__func__, UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS);
> +		err = -ETIMEDOUT;
> +	}
> +
> +out:
> +	return err;
> +}
> +
> +/**
> + * ufs_qcom_ice_resume() - resumes UFS-ICE interface and ICE device from
> power
> + * collapse
> + * @qcom_host:	Pointer to a UFS QCom internal host structure.
> + *		qcom_host, qcom_host->hba and qcom_host->hba->dev should
> all
> + *		be valid pointers.
> + *
> + * Return: -EINVAL in-case of an error
> + *         0 otherwise
> + */
> +int ufs_qcom_ice_resume(struct ufs_qcom_host *qcom_host)
> +{
> +	struct device *dev = qcom_host->hba->dev;
> +	int err = 0;
> +
> +	if (!qcom_host->ice.pdev) {
> +		dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
> +		goto out;
> +	}
> +
> +	if (qcom_host->ice.state !=
> +			UFS_QCOM_ICE_STATE_SUSPENDED) {
> +		goto out;
> +	}
> +
> +	if (!qcom_host->ice.vops) {
> +		dev_err(dev, "%s: invalid ice_vops\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	init_completion(&qcom_host->ice.async_done);
> +
> +	if (qcom_host->ice.vops->resume) {
> +		err = qcom_host->ice.vops->resume(qcom_host->ice.pdev);
> +		if (err) {
> +			dev_err(dev, "%s: ice_vops->resume failed. err
> %d\n",
> +				__func__, err);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	if (!wait_for_completion_timeout(&qcom_host->ice.async_done,
> +
> msecs_to_jiffies(UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS))) {
> +		dev_err(dev,
> +			"%s: error. got timeout after %d ms\n",
> +			__func__, UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS);
> +		err = -ETIMEDOUT;
> +		goto out;
> +	}
> +
> +	if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE)
> +		err = -EINVAL;
> +out:
> +	return err;
> +}
> +
> +/**
> + * ufs_qcom_ice_suspend() - suspends UFS-ICE interface and ICE device
> + * @qcom_host:	Pointer to a UFS QCom internal host structure.
> + *		qcom_host, qcom_host->hba and qcom_host->hba->dev should
> all
> + *		be valid pointers.
> + *
> + * Return: -EINVAL in-case of an error
> + *         0 otherwise
> + */
> +int ufs_qcom_ice_suspend(struct ufs_qcom_host *qcom_host)
> +{
> +	struct device *dev = qcom_host->hba->dev;
> +	int err = 0;
> +
> +	if (!qcom_host->ice.pdev) {
> +		dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
> +		goto out;
> +	}
> +
> +	if (qcom_host->ice.vops->suspend) {
> +		err = qcom_host->ice.vops->suspend(qcom_host->ice.pdev);
> +		if (err) {
> +			dev_err(qcom_host->hba->dev,
> +				"%s: ice_vops->suspend failed. err %d\n",
> +				__func__, err);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_ACTIVE) {
> +		qcom_host->ice.state = UFS_QCOM_ICE_STATE_SUSPENDED;
> +	} else if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_DISABLED) {
> +		dev_err(qcom_host->hba->dev,
> +				"%s: ice state is invalid: disabled\n",
> +				__func__);
> +		err = -EINVAL;
> +	}
> +
> +out:
> +	return err;
> +}
> +
> +/**
> + * ufs_qcom_ice_get_status() - returns the status of an ICE transaction
> + * @qcom_host:	Pointer to a UFS QCom internal host structure.
> + *		qcom_host, qcom_host->hba and qcom_host->hba->dev should
> all
> + *		be valid pointers.
> + * @ice_status:	Pointer to a valid output parameter.
> + *		< 0 in case of ICE transaction failure.
> + *		0 otherwise.
> + *
> + * Return: -EINVAL in-case of an error
> + *         0 otherwise
> + */
> +int ufs_qcom_ice_get_status(struct ufs_qcom_host *qcom_host, int
> *ice_status)
> +{
> +	struct device *dev = NULL;
> +	int err = 0;
> +	int stat = -EINVAL;
> +
> +	ice_status = 0;
> +
> +	dev = qcom_host->hba->dev;
> +	if (!dev) {
> +		err = -EINVAL;
> +		goto out;
> +	}
> +
> +	if (!qcom_host->ice.pdev) {
> +		dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
> +		goto out;
> +	}
> +
> +	if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) {
> +		err = -EINVAL;
> +		goto out;
> +	}
> +
> +	if (!qcom_host->ice.vops) {
> +		dev_err(dev, "%s: invalid ice_vops\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	if (qcom_host->ice.vops->status) {
> +		stat = qcom_host->ice.vops->status(qcom_host->ice.pdev);
> +		if (stat < 0) {
> +			dev_err(dev, "%s: ice_vops->status failed. stat
> %d\n",
> +				__func__, stat);
> +			err = -EINVAL;
> +			goto out;
> +		}
> +
> +		*ice_status = stat;
> +	}
> +
> +out:
> +	return err;
> +}
> diff --git a/drivers/scsi/ufs/ufs-qcom-ice.h
> b/drivers/scsi/ufs/ufs-qcom-ice.h
> new file mode 100644
> index 0000000..5ccbf5f
> --- /dev/null
> +++ b/drivers/scsi/ufs/ufs-qcom-ice.h
> @@ -0,0 +1,113 @@
> +/* Copyright (c) 2014-2015, 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.
> + *
> + */
> +
> +#ifndef _UFS_QCOM_ICE_H_
> +#define _UFS_QCOM_ICE_H_
> +
> +#include <scsi/scsi_cmnd.h>
> +
> +#include "ufs-qcom.h"
> +
> +/*
> + * UFS host controller ICE registers. There are n [0..31]
> + * of each of these registers
> + */
> +enum {
> +	REG_UFS_QCOM_ICE_CTRL_INFO_1_n           = 0x2204,
> +	REG_UFS_QCOM_ICE_CTRL_INFO_2_n           = 0x2208,
> +};
> +
> +/* UFS QCOM ICE CTRL Info 2 register offset */
> +enum {
> +	OFFSET_UFS_QCOM_ICE_CTRL_INFO_2_BYPASS     = 0,
> +	OFFSET_UFS_QCOM_ICE_CTRL_INFO_2_KEY_INDEX  = 0x1,
> +	OFFSET_UFS_QCOM_ICE_CTRL_INFO_2_CDU        = 0x6,
> +};
> +
> +/* UFS QCOM ICE CTRL Info 2 register masks */
> +enum {
> +	MASK_UFS_QCOM_ICE_CTRL_INFO_2_BYPASS     = 0x1,
> +	MASK_UFS_QCOM_ICE_CTRL_INFO_2_KEY_INDEX  = 0x1F,
> +	MASK_UFS_QCOM_ICE_CTRL_INFO_2_CDU        = 0x8,
> +};
> +
> +/* UFS QCOM ICE encryption/decryption bypass state */
> +enum {
> +	UFS_QCOM_ICE_DISABLE_BYPASS  = 0,
> +	UFS_QCOM_ICE_ENABLE_BYPASS = 1,
> +};
> +
> +/* UFS QCOM ICE Crypto Data Unit of target DUN of Transfer Request */
> +enum {
> +	UFS_QCOM_ICE_TR_DATA_UNIT_512_B          = 0,
> +	UFS_QCOM_ICE_TR_DATA_UNIT_1_KB           = 1,
> +	UFS_QCOM_ICE_TR_DATA_UNIT_2_KB           = 2,
> +	UFS_QCOM_ICE_TR_DATA_UNIT_4_KB           = 3,
> +	UFS_QCOM_ICE_TR_DATA_UNIT_8_KB           = 4,
> +	UFS_QCOM_ICE_TR_DATA_UNIT_16_KB          = 5,
> +	UFS_QCOM_ICE_TR_DATA_UNIT_32_KB          = 6,
> +};
> +
> +/* UFS QCOM ICE internal state */
> +enum {
> +	UFS_QCOM_ICE_STATE_DISABLED   = 0,
> +	UFS_QCOM_ICE_STATE_ACTIVE     = 1,
> +	UFS_QCOM_ICE_STATE_SUSPENDED  = 2,
> +};
> +
> +#ifdef CONFIG_SCSI_UFS_QCOM_ICE
> +int ufs_qcom_ice_get_dev(struct ufs_qcom_host *qcom_host);
> +int ufs_qcom_ice_init(struct ufs_qcom_host *qcom_host);
> +int ufs_qcom_ice_cfg(struct ufs_qcom_host *qcom_host, struct scsi_cmnd
> *cmd);
> +int ufs_qcom_ice_reset(struct ufs_qcom_host *qcom_host);
> +int ufs_qcom_ice_resume(struct ufs_qcom_host *qcom_host);
> +int ufs_qcom_ice_suspend(struct ufs_qcom_host *qcom_host);
> +int ufs_qcom_ice_get_status(struct ufs_qcom_host *qcom_host, int
> *ice_status);
> +#else
> +inline int ufs_qcom_ice_get_dev(struct ufs_qcom_host *qcom_host)
> +{
> +	if (qcom_host) {
> +		qcom_host->ice.pdev = NULL;
> +		qcom_host->ice.vops = NULL;
> +	}
> +	return -ENODEV;
> +}
> +inline int ufs_qcom_ice_init(struct ufs_qcom_host *qcom_host)
> +{
> +	return 0;
> +}
> +inline int ufs_qcom_ice_cfg(struct ufs_qcom_host *qcom_host,
> +			    struct scsi_cmnd *cmd)
> +{
> +	return 0;
> +}
> +inline int ufs_qcom_ice_reset(struct ufs_qcom_host *qcom_host)
> +{
> +	return 0;
> +}
> +inline int ufs_qcom_ice_resume(struct ufs_qcom_host *qcom_host)
> +{
> +	return 0;
> +}
> +inline int ufs_qcom_ice_suspend(struct ufs_qcom_host *qcom_host)
> +{
> +	return 0;
> +}
> +inline int ufs_qcom_ice_get_status(struct ufs_qcom_host *qcom_host,
> +				   int *ice_status)
> +{
> +	return 0;
> +}
> +#endif /* CONFIG_SCSI_UFS_QCOM_ICE */
> +
> +#endif /* UFS_QCOM_ICE_H_ */
> diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c
> index 9217af9..fd01255 100644
> --- a/drivers/scsi/ufs/ufs-qcom.c
> +++ b/drivers/scsi/ufs/ufs-qcom.c
> @@ -22,6 +22,7 @@
>  #include "unipro.h"
>  #include "ufs-qcom.h"
>  #include "ufshci.h"
> +#include "ufs-qcom-ice.h"
>
>  static struct ufs_qcom_host *ufs_qcom_hosts[MAX_UFS_QCOM_HOSTS];
>
> @@ -294,6 +295,13 @@ static int ufs_qcom_hce_enable_notify(struct ufs_hba
> *hba, bool status)
>  		/* check if UFS PHY moved from DISABLED to HIBERN8 */
>  		err = ufs_qcom_check_hibern8(hba);
>  		ufs_qcom_enable_hw_clk_gating(hba);
> +		if (!err) {
> +			err = ufs_qcom_ice_reset(host);
> +			if (err)
> +				dev_err(hba->dev,
> +					"%s: ufs_qcom_ice_reset() failed
> %d\n",
> +					__func__, err);
> +		}
>
>  		break;
>  	default:
> @@ -453,6 +461,10 @@ static int ufs_qcom_suspend(struct ufs_hba *hba, enum
> ufs_pm_op pm_op)
>  		 */
>  		ufs_qcom_disable_lane_clks(host);
>  		phy_power_off(phy);
> +		ret = ufs_qcom_ice_suspend(host);
> +		if (ret)
> +			dev_err(hba->dev, "%s: failed ufs_qcom_ice_suspend
> %d\n",
> +					__func__, ret);
>
>  		/* Assert PHY soft reset */
>  		ufs_qcom_assert_reset(hba);
> @@ -463,8 +475,10 @@ static int ufs_qcom_suspend(struct ufs_hba *hba, enum
> ufs_pm_op pm_op)
>  	 * If UniPro link is not active, PHY ref_clk, main PHY analog
> power
>  	 * rail and low noise analog power rail for PLL can be switched
> off.
>  	 */
> -	if (!ufs_qcom_is_link_active(hba))
> +	if (!ufs_qcom_is_link_active(hba)) {
>  		phy_power_off(phy);
> +		ufs_qcom_ice_suspend(host);
> +	}
>
>  out:
>  	return ret;
> @@ -483,6 +497,13 @@ static int ufs_qcom_resume(struct ufs_hba *hba, enum
> ufs_pm_op pm_op)
>  		goto out;
>  	}
>
> +	err = ufs_qcom_ice_resume(host);
> +	if (err) {
> +		dev_err(hba->dev, "%s: ufs_qcom_ice_resume failed, err =
> %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
>  	hba->is_sys_suspended = false;
>
>  out:
> @@ -917,6 +938,30 @@ static int ufs_qcom_init(struct ufs_hba *hba)
>  	host->hba = hba;
>  	hba->priv = (void *)host;
>
> +	err = ufs_qcom_ice_get_dev(host);
> +	if (err == -EPROBE_DEFER) {
> +		/*
> +		 * UFS driver might be probed before ICE driver does.
> +		 * In that case we would like to return EPROBE_DEFER code
> +		 * in order to delay its probing.
> +		 */
> +		dev_err(dev, "%s: required ICE device not probed yet err =
> %d\n",
> +			__func__, err);
> +		goto out_host_free;
> +
> +	} else if (err == -ENODEV) {
> +		/*
> +		 * ICE device is not enabled in DTS file. No need for
> further
> +		 * initialization of ICE driver.
> +		 */
> +		dev_warn(dev, "%s: ICE device is not enabled",
> +			__func__);
> +	} else if (err) {
> +		dev_err(dev, "%s: ufs_qcom_ice_get_dev failed %d\n",
> +			__func__, err);
> +		goto out_host_free;
> +	}
> +
>  	host->generic_phy = devm_phy_get(dev, "ufsphy");
>
>  	if (IS_ERR(host->generic_phy)) {
> @@ -944,6 +989,15 @@ static int ufs_qcom_init(struct ufs_hba *hba)
>  	hba->caps |= UFSHCD_CAP_AUTO_BKOPS_SUSPEND;
>
>  	ufs_qcom_setup_clocks(hba, true);
> +	if (host->ice.pdev) {
> +		err = ufs_qcom_ice_init(host);
> +		if (err) {
> +			dev_err(dev, "%s: ICE driver initialization failed
> (%d)\n",
> +				__func__, err);
> +			device_remove_file(dev,
> &host->bus_vote.max_bus_bw);
> +			goto out_disable_phy;
> +		}
> +	}
>
>  	if (hba->dev->id < MAX_UFS_QCOM_HOSTS)
>  		ufs_qcom_hosts[hba->dev->id] = host;
> diff --git a/drivers/scsi/ufs/ufs-qcom.h b/drivers/scsi/ufs/ufs-qcom.h
> index 9a6febd..8340f2c 100644
> --- a/drivers/scsi/ufs/ufs-qcom.h
> +++ b/drivers/scsi/ufs/ufs-qcom.h
> @@ -151,6 +151,30 @@ struct ufs_qcom_bus_vote {
>  	struct device_attribute max_bus_bw;
>  };
>
> +/**
> + * struct ufs_qcom_ice_data - ICE related information
> + * @vops:	pointer to variant operations of ICE
> + * @async_done:	completion for supporting ICE's driver
> asynchronous nature
> + * @pdev:	pointer to the proper ICE platform device
> + * @state:      UFS-ICE interface's internal state (see
> + *       ufs-qcom-ice.h for possible internal states)
> + * @quirks:     UFS-ICE interface related quirks
> + */
> +struct ufs_qcom_ice_data {
> +	struct qcom_ice_variant_ops *vops;
> +	struct completion async_done;
> +	struct platform_device *pdev;
> +	int state;
> +
> +	/*
> +	 * If UFS host controller should handle cryptographic engine's
> +	 * errors, enables this quirk.
> +	 */
> +	#define UFS_QCOM_ICE_QUIRK_HANDLE_CRYPTO_ENGINE_ERRORS	UFS_BIT(0)
> +
> +	u16 quirks;
> +};
> +
>  struct ufs_qcom_host {
>  	struct phy *generic_phy;
>  	struct ufs_hba *hba;
> @@ -161,6 +185,7 @@ struct ufs_qcom_host {
>  	struct clk *rx_l1_sync_clk;
>  	struct clk *tx_l1_sync_clk;
>  	bool is_lane_clks_enabled;
> +	struct ufs_qcom_ice_data ice;
>  };
>
>  #define ufs_qcom_is_link_off(hba) ufshcd_is_link_off(hba)
> --
> 1.8.5.2
>
> --
> QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
> of Code Aurora Forum, hosted by The Linux Foundation
> --
> To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v7 5/5] scsi: ufs-qcom-ice: add Inline Crypto Engine (ICE) support for UFS
@ 2015-01-15 15:21     ` Dov Levenglick
  0 siblings, 0 replies; 27+ messages in thread
From: Dov Levenglick @ 2015-01-15 15:21 UTC (permalink / raw)
  To: Yaniv Gardi
  Cc: james.bottomley, hch, linux-kernel, linux-scsi, linux-arm-msm,
	santoshsy, linux-scsi-owner, subhashj, ygardi, noag, draviv,
	Yaniv Gardi, Vinayak Holikatti, James E.J. Bottomley

Reviewed-by: Dov Levenglick <dovl@codeaurora.org>

> From: Yaniv Gardi <ygardi@qti.qualcomm.com>
>
> In-order to enhance storage encryption performance,
> an Inline Cryptographic Engine is introduced to UFS.
> This patch adds in-line encryption capabilities to the UFS
> driver.
>
> Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>
>
> ---
>  drivers/scsi/ufs/Kconfig        |  12 +
>  drivers/scsi/ufs/Makefile       |   1 +
>  drivers/scsi/ufs/ufs-qcom-ice.c | 520
> ++++++++++++++++++++++++++++++++++++++++
>  drivers/scsi/ufs/ufs-qcom-ice.h | 113 +++++++++
>  drivers/scsi/ufs/ufs-qcom.c     |  56 ++++-
>  drivers/scsi/ufs/ufs-qcom.h     |  25 ++
>  6 files changed, 726 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/scsi/ufs/ufs-qcom-ice.c
>  create mode 100644 drivers/scsi/ufs/ufs-qcom-ice.h
>
> diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig
> index 8a1f4b3..ecf34ed 100644
> --- a/drivers/scsi/ufs/Kconfig
> +++ b/drivers/scsi/ufs/Kconfig
> @@ -83,3 +83,15 @@ config SCSI_UFS_QCOM
>
>  	  Select this if you have UFS controller on QCOM chipset.
>  	  If unsure, say N.
> +
> +config SCSI_UFS_QCOM_ICE
> +	bool "QCOM specific hooks to Inline Crypto Engine for UFS driver"
> +	depends on SCSI_UFS_QCOM && CRYPTO_DEV_QCOM_ICE
> +	help
> +	  This selects the QCOM specific additions to support Inline
> Crypto
> +	  Engine (ICE).
> +	  ICE accelerates the crypto operations and maintains the high UFS
> +	  performance.
> +
> +	  Select this if you have ICE supported for UFS on QCOM chipset.
> +	  If unsure, say N.
> diff --git a/drivers/scsi/ufs/Makefile b/drivers/scsi/ufs/Makefile
> index 8303bcc..31adca5 100644
> --- a/drivers/scsi/ufs/Makefile
> +++ b/drivers/scsi/ufs/Makefile
> @@ -1,5 +1,6 @@
>  # UFSHCD makefile
>  obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-qcom.o
> +obj-$(CONFIG_SCSI_UFS_QCOM_ICE) += ufs-qcom-ice.o
>  obj-$(CONFIG_SCSI_UFSHCD) += ufshcd.o
>  obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o
>  obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o
> diff --git a/drivers/scsi/ufs/ufs-qcom-ice.c
> b/drivers/scsi/ufs/ufs-qcom-ice.c
> new file mode 100644
> index 0000000..9202b73
> --- /dev/null
> +++ b/drivers/scsi/ufs/ufs-qcom-ice.c
> @@ -0,0 +1,520 @@
> +/* Copyright (c) 2014-2015, 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/of.h>
> +#include <linux/async.h>
> +#include <linux/blkdev.h>
> +#include <crypto/ice.h>
> +
> +#include "ufs-qcom-ice.h"
> +#include "ufshcd.h"
> +
> +#define UFS_QCOM_CRYPTO_LABEL "ufs-qcom-crypto"
> +/* Timeout waiting for ICE initialization, that requires TZ access */
> +#define UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS 500
> +
> +static void ufs_qcom_ice_success_cb(void *host_ctrl,
> +				enum ice_event_completion evt)
> +{
> +	struct ufs_qcom_host *qcom_host = (struct ufs_qcom_host
> *)host_ctrl;
> +
> +	if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_DISABLED &&
> +	    evt == ICE_INIT_COMPLETION)
> +		qcom_host->ice.state = UFS_QCOM_ICE_STATE_ACTIVE;
> +	 else if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_SUSPENDED &&
> +		   evt == ICE_RESUME_COMPLETION)
> +		qcom_host->ice.state = UFS_QCOM_ICE_STATE_ACTIVE;
> +
> +	complete(&qcom_host->ice.async_done);
> +}
> +
> +static void ufs_qcom_ice_error_cb(void *host_ctrl, enum ice_error_code
> evt)
> +{
> +	struct ufs_qcom_host *qcom_host = (struct ufs_qcom_host
> *)host_ctrl;
> +
> +	dev_err(qcom_host->hba->dev, "%s: Error in ice operation %d",
> +		__func__, evt);
> +
> +	if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_ACTIVE)
> +		qcom_host->ice.state = UFS_QCOM_ICE_STATE_DISABLED;
> +
> +	complete(&qcom_host->ice.async_done);
> +}
> +
> +static struct platform_device *ufs_qcom_ice_get_pdevice(struct device
> *ufs_dev)
> +{
> +	struct device_node *node;
> +	struct platform_device *ice_pdev = NULL;
> +
> +	node = of_parse_phandle(ufs_dev->of_node, UFS_QCOM_CRYPTO_LABEL,
> 0);
> +
> +	if (!node) {
> +		dev_err(ufs_dev, "%s: ufs-qcom-crypto property not
> specified\n",
> +			__func__);
> +		goto out;
> +	}
> +
> +	ice_pdev = qcom_ice_get_pdevice(node);
> +out:
> +	return ice_pdev;
> +}
> +
> +static
> +struct qcom_ice_variant_ops *ufs_qcom_ice_get_vops(struct device
> *ufs_dev)
> +{
> +	struct qcom_ice_variant_ops *ice_vops = NULL;
> +	struct device_node *node;
> +
> +	node = of_parse_phandle(ufs_dev->of_node, UFS_QCOM_CRYPTO_LABEL,
> 0);
> +
> +	if (!node) {
> +		dev_err(ufs_dev, "%s: ufs-qcom-crypto property not
> specified\n",
> +			__func__);
> +		goto out;
> +	}
> +
> +	ice_vops = qcom_ice_get_variant_ops(node);
> +
> +	if (!ice_vops)
> +		dev_err(ufs_dev, "%s: invalid ice_vops\n", __func__);
> +
> +	of_node_put(node);
> +out:
> +	return ice_vops;
> +}
> +
> +/**
> + * ufs_qcom_ice_get_dev() - sets pointers to ICE data structs in UFS QCom
> host
> + * @qcom_host:	Pointer to a UFS QCom internal host structure.
> + *
> + * Sets ICE platform device pointer and ICE vops structure
> + * corresponding to the current UFS device.
> + *
> + * Return: -EINVAL in-case of invalid input parameters:
> + *  qcom_host, qcom_host->hba or qcom_host->hba->dev
> + *         -ENODEV in-case ICE device is not required
> + *         -EPROBE_DEFER in-case ICE is required and hasn't been probed
> yet
> + *         0 otherwise
> + */
> +int ufs_qcom_ice_get_dev(struct ufs_qcom_host *qcom_host)
> +{
> +	struct device *ufs_dev;
> +	int err = 0;
> +
> +	if (!qcom_host || !qcom_host->hba || !qcom_host->hba->dev) {
> +		pr_err("%s: invalid qcom_host %p or qcom_host->hba or
> qcom_host->hba->dev\n",
> +			__func__, qcom_host);
> +		err = -EINVAL;
> +		goto out;
> +	}
> +
> +	ufs_dev = qcom_host->hba->dev;
> +
> +	qcom_host->ice.vops  = ufs_qcom_ice_get_vops(ufs_dev);
> +	qcom_host->ice.pdev = ufs_qcom_ice_get_pdevice(ufs_dev);
> +
> +	if (qcom_host->ice.pdev == ERR_PTR(-EPROBE_DEFER)) {
> +		dev_err(ufs_dev, "%s: ICE device not probed yet\n",
> +			__func__);
> +		qcom_host->ice.pdev = NULL;
> +		qcom_host->ice.vops = NULL;
> +		err = -EPROBE_DEFER;
> +		goto out;
> +	}
> +
> +	if (!qcom_host->ice.pdev || !qcom_host->ice.vops) {
> +		dev_err(ufs_dev, "%s: invalid platform device %p or vops
> %p\n",
> +			__func__, qcom_host->ice.pdev,
> qcom_host->ice.vops);
> +		qcom_host->ice.pdev = NULL;
> +		qcom_host->ice.vops = NULL;
> +		err = -ENODEV;
> +		goto out;
> +	}
> +
> +	qcom_host->ice.state = UFS_QCOM_ICE_STATE_DISABLED;
> +
> +out:
> +	return err;
> +
> +}
> +
> +/**
> + * ufs_qcom_ice_init() - initializes the ICE-UFS interface and ICE device
> + * @qcom_host:	Pointer to a UFS QCom internal host structure.
> + *		qcom_host, qcom_host->hba and qcom_host->hba->dev should
> all
> + *		be valid pointers.
> + *
> + * Return: -EINVAL in-case of an error
> + *         0 otherwise
> + */
> +int ufs_qcom_ice_init(struct ufs_qcom_host *qcom_host)
> +{
> +	struct device *ufs_dev = qcom_host->hba->dev;
> +	int err = -EINVAL;
> +
> +	init_completion(&qcom_host->ice.async_done);
> +	err = qcom_host->ice.vops->init(qcom_host->ice.pdev,
> +				qcom_host,
> +				ufs_qcom_ice_success_cb,
> +				ufs_qcom_ice_error_cb);
> +	if (err) {
> +		dev_err(ufs_dev, "%s: ice init failed. err = %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	if (!wait_for_completion_timeout(&qcom_host->ice.async_done,
> +
> msecs_to_jiffies(UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS))) {
> +		dev_err(qcom_host->hba->dev,
> +			"%s: error. got timeout after %d ms\n",
> +			__func__, UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS);
> +		err = -ETIMEDOUT;
> +		goto out;
> +	}
> +
> +	if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) {
> +		dev_err(qcom_host->hba->dev,
> +			"%s: error. ice.state (%d) is not in active
> state\n",
> +			__func__, qcom_host->ice.state);
> +		err = -EINVAL;
> +	}
> +
> +out:
> +	return err;
> +}
> +
> +static inline bool ufs_qcom_is_data_cmd(char cmd_op, bool is_write)
> +{
> +	if (is_write) {
> +		if (cmd_op == WRITE_6 || cmd_op == WRITE_10 ||
> +		    cmd_op == WRITE_16)
> +			return true;
> +	} else {
> +		if (cmd_op == READ_6 || cmd_op == READ_10 ||
> +		    cmd_op == READ_16)
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +/**
> + * ufs_qcom_ice_cfg() - configures UFS's ICE registers for an ICE
> transaction
> + * @qcom_host:	Pointer to a UFS QCom internal host structure.
> + *		qcom_host, qcom_host->hba and qcom_host->hba->dev should
> all
> + *		be valid pointers.
> + * @cmd:	Pointer to a valid scsi command. cmd->request should also
> be
> + *              a valid pointer.
> + *
> + * Return: -EINVAL in-case of an error
> + *         0 otherwise
> + */
> +int ufs_qcom_ice_cfg(struct ufs_qcom_host *qcom_host, struct scsi_cmnd
> *cmd)
> +{
> +	struct device *dev = qcom_host->hba->dev;
> +	int err = 0;
> +	struct ice_data_setting ice_set;
> +	unsigned int slot = 0;
> +	sector_t lba = 0;
> +	unsigned int ctrl_info_2_val = 0;
> +	unsigned int bypass = 0;
> +	struct request *req;
> +	char cmd_op;
> +
> +	if (!qcom_host->ice.pdev || !qcom_host->ice.vops) {
> +		dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
> +		goto out;
> +	}
> +
> +	if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) {
> +		dev_err(dev, "%s: ice state (%d) is not active\n",
> +			__func__, qcom_host->ice.state);
> +		return -EINVAL;
> +	}
> +
> +	req = cmd->request;
> +	if (req->bio)
> +		lba = req->bio->bi_sector;
> +
> +	slot = req->tag;
> +	if (slot < 0 || slot > qcom_host->hba->nutrs) {
> +		dev_err(dev, "%s: slot (%d) is out of boundaries
> (0...%d)\n",
> +			__func__, slot, qcom_host->hba->nutrs);
> +		return -EINVAL;
> +	}
> +
> +	memset(&ice_set, sizeof(ice_set), 0);
> +	if (qcom_host->ice.vops->config) {
> +		err = qcom_host->ice.vops->config(qcom_host->ice.pdev,
> +							req, &ice_set);
> +
> +		if (err) {
> +			dev_err(dev, "%s: error in ice_vops->config %d\n",
> +				__func__, err);
> +			goto out;
> +		}
> +	}
> +
> +	cmd_op = cmd->cmnd[0];
> +
> +#define UFS_QCOM_DIR_WRITE	true
> +#define UFS_QCOM_DIR_READ	false
> +	/* if non data command, bypass shall be enabled */
> +	if (!ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_WRITE) &&
> +	    !ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_READ))
> +		bypass = UFS_QCOM_ICE_ENABLE_BYPASS;
> +	/* if writing data command */
> +	else if (ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_WRITE))
> +		bypass = ice_set.encr_bypass ? UFS_QCOM_ICE_ENABLE_BYPASS
> :
> +
> UFS_QCOM_ICE_DISABLE_BYPASS;
> +	/* if reading data command */
> +	else if (ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_READ))
> +		bypass = ice_set.decr_bypass ? UFS_QCOM_ICE_ENABLE_BYPASS
> :
> +
> UFS_QCOM_ICE_DISABLE_BYPASS;
> +
> +	/* Configure ICE index */
> +	ctrl_info_2_val =
> +		(ice_set.crypto_data.key_index &
> +		 MASK_UFS_QCOM_ICE_CTRL_INFO_2_KEY_INDEX)
> +		 << OFFSET_UFS_QCOM_ICE_CTRL_INFO_2_KEY_INDEX;
> +
> +	/* Configure data unit size of transfer request */
> +	ctrl_info_2_val |=
> +		(UFS_QCOM_ICE_TR_DATA_UNIT_4_KB &
> +		 MASK_UFS_QCOM_ICE_CTRL_INFO_2_CDU)
> +		 << OFFSET_UFS_QCOM_ICE_CTRL_INFO_2_CDU;
> +
> +	/* Configure ICE bypass mode */
> +	ctrl_info_2_val |=
> +		(bypass & MASK_UFS_QCOM_ICE_CTRL_INFO_2_BYPASS)
> +		 << OFFSET_UFS_QCOM_ICE_CTRL_INFO_2_BYPASS;
> +
> +	ufshcd_writel(qcom_host->hba, lba,
> +		     (REG_UFS_QCOM_ICE_CTRL_INFO_1_n + 8 * slot));
> +
> +	ufshcd_writel(qcom_host->hba, ctrl_info_2_val,
> +		     (REG_UFS_QCOM_ICE_CTRL_INFO_2_n + 8 * slot));
> +
> +	/*
> +	 * Ensure UFS-ICE registers are being configured
> +	 * before next operation, otherwise UFS Host Controller might
> +	 * set get errors
> +	 */
> +	mb();
> +out:
> +	return err;
> +}
> +
> +/**
> + * ufs_qcom_ice_reset() - resets UFS-ICE interface and ICE device
> + * @qcom_host:	Pointer to a UFS QCom internal host structure.
> + *		qcom_host, qcom_host->hba and qcom_host->hba->dev should
> all
> + *		be valid pointers.
> + *
> + * Return: -EINVAL in-case of an error
> + *         0 otherwise
> + */
> +int ufs_qcom_ice_reset(struct ufs_qcom_host *qcom_host)
> +{
> +	struct device *dev = qcom_host->hba->dev;
> +	int err = 0;
> +
> +	if (!qcom_host->ice.pdev) {
> +		dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
> +		goto out;
> +	}
> +
> +	if (!qcom_host->ice.vops) {
> +		dev_err(dev, "%s: invalid ice_vops\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE)
> +		goto out;
> +
> +	init_completion(&qcom_host->ice.async_done);
> +
> +	if (qcom_host->ice.vops->reset) {
> +		err = qcom_host->ice.vops->reset(qcom_host->ice.pdev);
> +		if (err) {
> +			dev_err(dev, "%s: ice_vops->reset failed. err
> %d\n",
> +				__func__, err);
> +			goto out;
> +		}
> +	}
> +
> +	if (!wait_for_completion_timeout(&qcom_host->ice.async_done,
> +	     msecs_to_jiffies(UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS))) {
> +		dev_err(dev,
> +			"%s: error. got timeout after %d ms\n",
> +			__func__, UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS);
> +		err = -ETIMEDOUT;
> +	}
> +
> +out:
> +	return err;
> +}
> +
> +/**
> + * ufs_qcom_ice_resume() - resumes UFS-ICE interface and ICE device from
> power
> + * collapse
> + * @qcom_host:	Pointer to a UFS QCom internal host structure.
> + *		qcom_host, qcom_host->hba and qcom_host->hba->dev should
> all
> + *		be valid pointers.
> + *
> + * Return: -EINVAL in-case of an error
> + *         0 otherwise
> + */
> +int ufs_qcom_ice_resume(struct ufs_qcom_host *qcom_host)
> +{
> +	struct device *dev = qcom_host->hba->dev;
> +	int err = 0;
> +
> +	if (!qcom_host->ice.pdev) {
> +		dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
> +		goto out;
> +	}
> +
> +	if (qcom_host->ice.state !=
> +			UFS_QCOM_ICE_STATE_SUSPENDED) {
> +		goto out;
> +	}
> +
> +	if (!qcom_host->ice.vops) {
> +		dev_err(dev, "%s: invalid ice_vops\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	init_completion(&qcom_host->ice.async_done);
> +
> +	if (qcom_host->ice.vops->resume) {
> +		err = qcom_host->ice.vops->resume(qcom_host->ice.pdev);
> +		if (err) {
> +			dev_err(dev, "%s: ice_vops->resume failed. err
> %d\n",
> +				__func__, err);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	if (!wait_for_completion_timeout(&qcom_host->ice.async_done,
> +
> msecs_to_jiffies(UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS))) {
> +		dev_err(dev,
> +			"%s: error. got timeout after %d ms\n",
> +			__func__, UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS);
> +		err = -ETIMEDOUT;
> +		goto out;
> +	}
> +
> +	if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE)
> +		err = -EINVAL;
> +out:
> +	return err;
> +}
> +
> +/**
> + * ufs_qcom_ice_suspend() - suspends UFS-ICE interface and ICE device
> + * @qcom_host:	Pointer to a UFS QCom internal host structure.
> + *		qcom_host, qcom_host->hba and qcom_host->hba->dev should
> all
> + *		be valid pointers.
> + *
> + * Return: -EINVAL in-case of an error
> + *         0 otherwise
> + */
> +int ufs_qcom_ice_suspend(struct ufs_qcom_host *qcom_host)
> +{
> +	struct device *dev = qcom_host->hba->dev;
> +	int err = 0;
> +
> +	if (!qcom_host->ice.pdev) {
> +		dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
> +		goto out;
> +	}
> +
> +	if (qcom_host->ice.vops->suspend) {
> +		err = qcom_host->ice.vops->suspend(qcom_host->ice.pdev);
> +		if (err) {
> +			dev_err(qcom_host->hba->dev,
> +				"%s: ice_vops->suspend failed. err %d\n",
> +				__func__, err);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_ACTIVE) {
> +		qcom_host->ice.state = UFS_QCOM_ICE_STATE_SUSPENDED;
> +	} else if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_DISABLED) {
> +		dev_err(qcom_host->hba->dev,
> +				"%s: ice state is invalid: disabled\n",
> +				__func__);
> +		err = -EINVAL;
> +	}
> +
> +out:
> +	return err;
> +}
> +
> +/**
> + * ufs_qcom_ice_get_status() - returns the status of an ICE transaction
> + * @qcom_host:	Pointer to a UFS QCom internal host structure.
> + *		qcom_host, qcom_host->hba and qcom_host->hba->dev should
> all
> + *		be valid pointers.
> + * @ice_status:	Pointer to a valid output parameter.
> + *		< 0 in case of ICE transaction failure.
> + *		0 otherwise.
> + *
> + * Return: -EINVAL in-case of an error
> + *         0 otherwise
> + */
> +int ufs_qcom_ice_get_status(struct ufs_qcom_host *qcom_host, int
> *ice_status)
> +{
> +	struct device *dev = NULL;
> +	int err = 0;
> +	int stat = -EINVAL;
> +
> +	ice_status = 0;
> +
> +	dev = qcom_host->hba->dev;
> +	if (!dev) {
> +		err = -EINVAL;
> +		goto out;
> +	}
> +
> +	if (!qcom_host->ice.pdev) {
> +		dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
> +		goto out;
> +	}
> +
> +	if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) {
> +		err = -EINVAL;
> +		goto out;
> +	}
> +
> +	if (!qcom_host->ice.vops) {
> +		dev_err(dev, "%s: invalid ice_vops\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	if (qcom_host->ice.vops->status) {
> +		stat = qcom_host->ice.vops->status(qcom_host->ice.pdev);
> +		if (stat < 0) {
> +			dev_err(dev, "%s: ice_vops->status failed. stat
> %d\n",
> +				__func__, stat);
> +			err = -EINVAL;
> +			goto out;
> +		}
> +
> +		*ice_status = stat;
> +	}
> +
> +out:
> +	return err;
> +}
> diff --git a/drivers/scsi/ufs/ufs-qcom-ice.h
> b/drivers/scsi/ufs/ufs-qcom-ice.h
> new file mode 100644
> index 0000000..5ccbf5f
> --- /dev/null
> +++ b/drivers/scsi/ufs/ufs-qcom-ice.h
> @@ -0,0 +1,113 @@
> +/* Copyright (c) 2014-2015, 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.
> + *
> + */
> +
> +#ifndef _UFS_QCOM_ICE_H_
> +#define _UFS_QCOM_ICE_H_
> +
> +#include <scsi/scsi_cmnd.h>
> +
> +#include "ufs-qcom.h"
> +
> +/*
> + * UFS host controller ICE registers. There are n [0..31]
> + * of each of these registers
> + */
> +enum {
> +	REG_UFS_QCOM_ICE_CTRL_INFO_1_n           = 0x2204,
> +	REG_UFS_QCOM_ICE_CTRL_INFO_2_n           = 0x2208,
> +};
> +
> +/* UFS QCOM ICE CTRL Info 2 register offset */
> +enum {
> +	OFFSET_UFS_QCOM_ICE_CTRL_INFO_2_BYPASS     = 0,
> +	OFFSET_UFS_QCOM_ICE_CTRL_INFO_2_KEY_INDEX  = 0x1,
> +	OFFSET_UFS_QCOM_ICE_CTRL_INFO_2_CDU        = 0x6,
> +};
> +
> +/* UFS QCOM ICE CTRL Info 2 register masks */
> +enum {
> +	MASK_UFS_QCOM_ICE_CTRL_INFO_2_BYPASS     = 0x1,
> +	MASK_UFS_QCOM_ICE_CTRL_INFO_2_KEY_INDEX  = 0x1F,
> +	MASK_UFS_QCOM_ICE_CTRL_INFO_2_CDU        = 0x8,
> +};
> +
> +/* UFS QCOM ICE encryption/decryption bypass state */
> +enum {
> +	UFS_QCOM_ICE_DISABLE_BYPASS  = 0,
> +	UFS_QCOM_ICE_ENABLE_BYPASS = 1,
> +};
> +
> +/* UFS QCOM ICE Crypto Data Unit of target DUN of Transfer Request */
> +enum {
> +	UFS_QCOM_ICE_TR_DATA_UNIT_512_B          = 0,
> +	UFS_QCOM_ICE_TR_DATA_UNIT_1_KB           = 1,
> +	UFS_QCOM_ICE_TR_DATA_UNIT_2_KB           = 2,
> +	UFS_QCOM_ICE_TR_DATA_UNIT_4_KB           = 3,
> +	UFS_QCOM_ICE_TR_DATA_UNIT_8_KB           = 4,
> +	UFS_QCOM_ICE_TR_DATA_UNIT_16_KB          = 5,
> +	UFS_QCOM_ICE_TR_DATA_UNIT_32_KB          = 6,
> +};
> +
> +/* UFS QCOM ICE internal state */
> +enum {
> +	UFS_QCOM_ICE_STATE_DISABLED   = 0,
> +	UFS_QCOM_ICE_STATE_ACTIVE     = 1,
> +	UFS_QCOM_ICE_STATE_SUSPENDED  = 2,
> +};
> +
> +#ifdef CONFIG_SCSI_UFS_QCOM_ICE
> +int ufs_qcom_ice_get_dev(struct ufs_qcom_host *qcom_host);
> +int ufs_qcom_ice_init(struct ufs_qcom_host *qcom_host);
> +int ufs_qcom_ice_cfg(struct ufs_qcom_host *qcom_host, struct scsi_cmnd
> *cmd);
> +int ufs_qcom_ice_reset(struct ufs_qcom_host *qcom_host);
> +int ufs_qcom_ice_resume(struct ufs_qcom_host *qcom_host);
> +int ufs_qcom_ice_suspend(struct ufs_qcom_host *qcom_host);
> +int ufs_qcom_ice_get_status(struct ufs_qcom_host *qcom_host, int
> *ice_status);
> +#else
> +inline int ufs_qcom_ice_get_dev(struct ufs_qcom_host *qcom_host)
> +{
> +	if (qcom_host) {
> +		qcom_host->ice.pdev = NULL;
> +		qcom_host->ice.vops = NULL;
> +	}
> +	return -ENODEV;
> +}
> +inline int ufs_qcom_ice_init(struct ufs_qcom_host *qcom_host)
> +{
> +	return 0;
> +}
> +inline int ufs_qcom_ice_cfg(struct ufs_qcom_host *qcom_host,
> +			    struct scsi_cmnd *cmd)
> +{
> +	return 0;
> +}
> +inline int ufs_qcom_ice_reset(struct ufs_qcom_host *qcom_host)
> +{
> +	return 0;
> +}
> +inline int ufs_qcom_ice_resume(struct ufs_qcom_host *qcom_host)
> +{
> +	return 0;
> +}
> +inline int ufs_qcom_ice_suspend(struct ufs_qcom_host *qcom_host)
> +{
> +	return 0;
> +}
> +inline int ufs_qcom_ice_get_status(struct ufs_qcom_host *qcom_host,
> +				   int *ice_status)
> +{
> +	return 0;
> +}
> +#endif /* CONFIG_SCSI_UFS_QCOM_ICE */
> +
> +#endif /* UFS_QCOM_ICE_H_ */
> diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c
> index 9217af9..fd01255 100644
> --- a/drivers/scsi/ufs/ufs-qcom.c
> +++ b/drivers/scsi/ufs/ufs-qcom.c
> @@ -22,6 +22,7 @@
>  #include "unipro.h"
>  #include "ufs-qcom.h"
>  #include "ufshci.h"
> +#include "ufs-qcom-ice.h"
>
>  static struct ufs_qcom_host *ufs_qcom_hosts[MAX_UFS_QCOM_HOSTS];
>
> @@ -294,6 +295,13 @@ static int ufs_qcom_hce_enable_notify(struct ufs_hba
> *hba, bool status)
>  		/* check if UFS PHY moved from DISABLED to HIBERN8 */
>  		err = ufs_qcom_check_hibern8(hba);
>  		ufs_qcom_enable_hw_clk_gating(hba);
> +		if (!err) {
> +			err = ufs_qcom_ice_reset(host);
> +			if (err)
> +				dev_err(hba->dev,
> +					"%s: ufs_qcom_ice_reset() failed
> %d\n",
> +					__func__, err);
> +		}
>
>  		break;
>  	default:
> @@ -453,6 +461,10 @@ static int ufs_qcom_suspend(struct ufs_hba *hba, enum
> ufs_pm_op pm_op)
>  		 */
>  		ufs_qcom_disable_lane_clks(host);
>  		phy_power_off(phy);
> +		ret = ufs_qcom_ice_suspend(host);
> +		if (ret)
> +			dev_err(hba->dev, "%s: failed ufs_qcom_ice_suspend
> %d\n",
> +					__func__, ret);
>
>  		/* Assert PHY soft reset */
>  		ufs_qcom_assert_reset(hba);
> @@ -463,8 +475,10 @@ static int ufs_qcom_suspend(struct ufs_hba *hba, enum
> ufs_pm_op pm_op)
>  	 * If UniPro link is not active, PHY ref_clk, main PHY analog
> power
>  	 * rail and low noise analog power rail for PLL can be switched
> off.
>  	 */
> -	if (!ufs_qcom_is_link_active(hba))
> +	if (!ufs_qcom_is_link_active(hba)) {
>  		phy_power_off(phy);
> +		ufs_qcom_ice_suspend(host);
> +	}
>
>  out:
>  	return ret;
> @@ -483,6 +497,13 @@ static int ufs_qcom_resume(struct ufs_hba *hba, enum
> ufs_pm_op pm_op)
>  		goto out;
>  	}
>
> +	err = ufs_qcom_ice_resume(host);
> +	if (err) {
> +		dev_err(hba->dev, "%s: ufs_qcom_ice_resume failed, err =
> %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
>  	hba->is_sys_suspended = false;
>
>  out:
> @@ -917,6 +938,30 @@ static int ufs_qcom_init(struct ufs_hba *hba)
>  	host->hba = hba;
>  	hba->priv = (void *)host;
>
> +	err = ufs_qcom_ice_get_dev(host);
> +	if (err == -EPROBE_DEFER) {
> +		/*
> +		 * UFS driver might be probed before ICE driver does.
> +		 * In that case we would like to return EPROBE_DEFER code
> +		 * in order to delay its probing.
> +		 */
> +		dev_err(dev, "%s: required ICE device not probed yet err =
> %d\n",
> +			__func__, err);
> +		goto out_host_free;
> +
> +	} else if (err == -ENODEV) {
> +		/*
> +		 * ICE device is not enabled in DTS file. No need for
> further
> +		 * initialization of ICE driver.
> +		 */
> +		dev_warn(dev, "%s: ICE device is not enabled",
> +			__func__);
> +	} else if (err) {
> +		dev_err(dev, "%s: ufs_qcom_ice_get_dev failed %d\n",
> +			__func__, err);
> +		goto out_host_free;
> +	}
> +
>  	host->generic_phy = devm_phy_get(dev, "ufsphy");
>
>  	if (IS_ERR(host->generic_phy)) {
> @@ -944,6 +989,15 @@ static int ufs_qcom_init(struct ufs_hba *hba)
>  	hba->caps |= UFSHCD_CAP_AUTO_BKOPS_SUSPEND;
>
>  	ufs_qcom_setup_clocks(hba, true);
> +	if (host->ice.pdev) {
> +		err = ufs_qcom_ice_init(host);
> +		if (err) {
> +			dev_err(dev, "%s: ICE driver initialization failed
> (%d)\n",
> +				__func__, err);
> +			device_remove_file(dev,
> &host->bus_vote.max_bus_bw);
> +			goto out_disable_phy;
> +		}
> +	}
>
>  	if (hba->dev->id < MAX_UFS_QCOM_HOSTS)
>  		ufs_qcom_hosts[hba->dev->id] = host;
> diff --git a/drivers/scsi/ufs/ufs-qcom.h b/drivers/scsi/ufs/ufs-qcom.h
> index 9a6febd..8340f2c 100644
> --- a/drivers/scsi/ufs/ufs-qcom.h
> +++ b/drivers/scsi/ufs/ufs-qcom.h
> @@ -151,6 +151,30 @@ struct ufs_qcom_bus_vote {
>  	struct device_attribute max_bus_bw;
>  };
>
> +/**
> + * struct ufs_qcom_ice_data - ICE related information
> + * @vops:	pointer to variant operations of ICE
> + * @async_done:	completion for supporting ICE's driver
> asynchronous nature
> + * @pdev:	pointer to the proper ICE platform device
> + * @state:      UFS-ICE interface's internal state (see
> + *       ufs-qcom-ice.h for possible internal states)
> + * @quirks:     UFS-ICE interface related quirks
> + */
> +struct ufs_qcom_ice_data {
> +	struct qcom_ice_variant_ops *vops;
> +	struct completion async_done;
> +	struct platform_device *pdev;
> +	int state;
> +
> +	/*
> +	 * If UFS host controller should handle cryptographic engine's
> +	 * errors, enables this quirk.
> +	 */
> +	#define UFS_QCOM_ICE_QUIRK_HANDLE_CRYPTO_ENGINE_ERRORS	UFS_BIT(0)
> +
> +	u16 quirks;
> +};
> +
>  struct ufs_qcom_host {
>  	struct phy *generic_phy;
>  	struct ufs_hba *hba;
> @@ -161,6 +185,7 @@ struct ufs_qcom_host {
>  	struct clk *rx_l1_sync_clk;
>  	struct clk *tx_l1_sync_clk;
>  	bool is_lane_clks_enabled;
> +	struct ufs_qcom_ice_data ice;
>  };
>
>  #define ufs_qcom_is_link_off(hba) ufshcd_is_link_off(hba)
> --
> 1.8.5.2
>
> --
> QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
> of Code Aurora Forum, hosted by The Linux Foundation
> --
> To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>



^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v7 2/5] phy: qcom-ufs: add support for 20nm phy
  2015-01-15 14:32   ` Yaniv Gardi
@ 2015-01-21  9:32     ` Kishon Vijay Abraham I
  -1 siblings, 0 replies; 27+ messages in thread
From: Kishon Vijay Abraham I @ 2015-01-21  9:32 UTC (permalink / raw)
  To: Yaniv Gardi, James.Bottomley, hch
  Cc: linux-kernel, linux-scsi, linux-arm-msm, santoshsy,
	linux-scsi-owner, subhashj, noag, draviv, Grant Likely,
	Rob Herring, open list:OPEN FIRMWARE AND...

hi,

On Thursday 15 January 2015 08:02 PM, Yaniv Gardi wrote:
> This change adds a support for a 20nm qcom-ufs phy that is required in
> platforms that use ufs-qcom controller.
> 
> Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>
> 
> ---
>  drivers/phy/Makefile                |   1 +
>  drivers/phy/phy-qcom-ufs-i.h        |  43 +++++-
>  drivers/phy/phy-qcom-ufs-qmp-20nm.c | 257 ++++++++++++++++++++++++++++++++++++
>  drivers/phy/phy-qcom-ufs-qmp-20nm.h | 235 +++++++++++++++++++++++++++++++++
>  include/linux/phy/phy-qcom-ufs.h    |  59 +++++++++
>  5 files changed, 594 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.c
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.h
>  create mode 100644 include/linux/phy/phy-qcom-ufs.h
> 
> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
> index 335965d..781b2fa 100644
> --- a/drivers/phy/Makefile
> +++ b/drivers/phy/Makefile
> @@ -35,3 +35,4 @@ obj-$(CONFIG_PHY_XGENE)			+= phy-xgene.o
>  obj-$(CONFIG_PHY_STIH407_USB)		+= phy-stih407-usb.o
>  obj-$(CONFIG_PHY_STIH41X_USB)		+= phy-stih41x-usb.o
>  obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs.o
> +obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs-qmp-20nm.o
> diff --git a/drivers/phy/phy-qcom-ufs-i.h b/drivers/phy/phy-qcom-ufs-i.h
> index dac200f..591a391 100644
> --- a/drivers/phy/phy-qcom-ufs-i.h
> +++ b/drivers/phy/phy-qcom-ufs-i.h
> @@ -15,15 +15,56 @@
>  #ifndef UFS_QCOM_PHY_I_H_
>  #define UFS_QCOM_PHY_I_H_
>  
> +#include <linux/module.h>
>  #include <linux/clk.h>
> +#include <linux/regulator/consumer.h>
>  #include <linux/slab.h>
> -#include <linux/phy/phy.h>
> +#include <linux/phy/phy-qcom-ufs.h>
>  #include <linux/platform_device.h>
>  #include <linux/io.h>
>  #include <linux/delay.h>
>  
> +#define readl_poll_timeout(addr, val, cond, sleep_us, timeout_us) \
> +({ \
> +	ktime_t timeout = ktime_add_us(ktime_get(), timeout_us); \
> +	might_sleep_if(timeout_us); \
> +	for (;;) { \
> +		(val) = readl(addr); \
> +		if (cond) \
> +			break; \
> +		if (timeout_us && ktime_compare(ktime_get(), timeout) > 0) { \
> +			(val) = readl(addr); \
> +			break; \
> +		} \
> +		if (sleep_us) \
> +			usleep_range(DIV_ROUND_UP(sleep_us, 4), sleep_us); \
> +	} \
> +	(cond) ? 0 : -ETIMEDOUT; \
> +})
> +
> +#define UFS_QCOM_PHY_CAL_ENTRY(reg, val)	\
> +	{				\
> +		.reg_offset = reg,	\
> +		.cfg_value = val,	\
> +	}
> +
>  #define UFS_QCOM_PHY_NAME_LEN	30
>  
> +enum {
> +	MASK_SERDES_START       = 0x1,
> +	MASK_PCS_READY          = 0x1,
> +};
> +
> +enum {
> +	OFFSET_SERDES_START     = 0x0,
> +};
> +
> +struct ufs_qcom_phy_stored_attributes {
> +	u32 att;
> +	u32 value;
> +};
> +
> +
>  struct ufs_qcom_phy_calibration {
>  	u32 reg_offset;
>  	u32 cfg_value;
> diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.c b/drivers/phy/phy-qcom-ufs-qmp-20nm.c
> new file mode 100644
> index 0000000..8332f96
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs-qmp-20nm.c
> @@ -0,0 +1,257 @@
> +/*
> + * Copyright (c) 2013-2015, 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 "phy-qcom-ufs-qmp-20nm.h"
> +
> +#define UFS_PHY_NAME "ufs_phy_qmp_20nm"
> +
> +static
> +int ufs_qcom_phy_qmp_20nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
> +					bool is_rate_B)
> +{
> +	struct ufs_qcom_phy_calibration *tbl_A, *tbl_B;
> +	int tbl_size_A, tbl_size_B;
> +	u8 major = ufs_qcom_phy->host_ctrl_rev_major;
> +	u16 minor = ufs_qcom_phy->host_ctrl_rev_minor;
> +	u16 step = ufs_qcom_phy->host_ctrl_rev_step;
> +	int err;
> +
> +	if ((major == 0x1) && (minor == 0x002) && (step == 0x0000)) {
> +		tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_2_0);
> +		tbl_A = phy_cal_table_rate_A_1_2_0;
> +	} else if ((major == 0x1) && (minor == 0x003) && (step == 0x0000)) {
> +		tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_3_0);
> +		tbl_A = phy_cal_table_rate_A_1_3_0;
> +	} else {
> +		dev_err(ufs_qcom_phy->dev, "%s: Unknown UFS-PHY version, no calibration values\n",
> +			__func__);
> +		err = -ENODEV;
> +		goto out;
> +	}
> +
> +	tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
> +	tbl_B = phy_cal_table_rate_B;
> +
> +	err = ufs_qcom_phy_calibrate(ufs_qcom_phy, tbl_A, tbl_size_A,
> +						tbl_B, tbl_size_B, is_rate_B);
> +
> +	if (err)
> +		dev_err(ufs_qcom_phy->dev, "%s: ufs_qcom_phy_calibrate() failed %d\n",
> +			__func__, err);
> +
> +out:
> +	return err;
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_20nm_advertise_quirks(struct ufs_qcom_phy *phy_common)
> +{
> +	phy_common->quirks =
> +		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
> +}
> +
> +static int ufs_qcom_phy_qmp_20nm_init(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy_qmp_20nm *phy = phy_get_drvdata(generic_phy);
> +	struct ufs_qcom_phy *phy_common = &phy->common_cfg;
> +	int err = 0;
> +
> +	err = ufs_qcom_phy_init_clks(generic_phy, phy_common);
> +	if (err) {
> +		dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_clks() failed %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	err = ufs_qcom_phy_init_vregulators(generic_phy, phy_common);
> +	if (err) {
> +		dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_vregulators() failed %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	ufs_qcom_phy_qmp_20nm_advertise_quirks(phy_common);
> +
> +out:
> +	return err;
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_20nm_power_control(struct ufs_qcom_phy *phy, bool val)
> +{
> +	bool hibern8_exit_after_pwr_collapse = phy->quirks &
> +		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
> +
> +	if (val) {
> +		writel_relaxed(0x1, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
> +		/*
> +		 * Before any transactions involving PHY, ensure PHY knows
> +		 * that it's analog rail is powered ON.
> +		 */
> +		mb();
> +
> +		if (hibern8_exit_after_pwr_collapse) {
> +			/*
> +			 * Give atleast 1us delay after restoring PHY analog
> +			 * power.
> +			 */
> +			usleep_range(1, 2);
> +			writel_relaxed(0x0A, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			writel_relaxed(0x08, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			/*
> +			 * Make sure workaround is deactivated before proceeding
> +			 * with normal PHY operations.
> +			 */
> +			mb();
> +		}
> +	} else {
> +		if (hibern8_exit_after_pwr_collapse) {
> +			writel_relaxed(0x0A, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			writel_relaxed(0x02, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			/*
> +			 * Make sure that above workaround is activated before
> +			 * PHY analog power collapse.
> +			 */
> +			mb();
> +		}
> +
> +		writel_relaxed(0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
> +		/*
> +		 * ensure that PHY knows its PHY analog rail is going
> +		 * to be powered down
> +		 */
> +		mb();
> +	}
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_20nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val)
> +{
> +	writel_relaxed(val & UFS_PHY_TX_LANE_ENABLE_MASK,
> +			phy->mmio + UFS_PHY_TX_LANE_ENABLE);
> +	mb();
> +}
> +
> +static inline void ufs_qcom_phy_qmp_20nm_start_serdes(struct ufs_qcom_phy *phy)
> +{
> +	u32 tmp;
> +
> +	tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
> +	tmp &= ~MASK_SERDES_START;
> +	tmp |= (1 << OFFSET_SERDES_START);
> +	writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
> +	mb();
> +}
> +
> +static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
> +{
> +	int err = 0;
> +	u32 val;
> +
> +	err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS,
> +			val, (val & MASK_PCS_READY), 10, 1000000);
> +	if (err)
> +		dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n",
> +			__func__, err);
> +	return err;
> +}
> +
> +static struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
> +	.init		= ufs_qcom_phy_qmp_20nm_init,
> +	.exit		= ufs_qcom_phy_exit,
> +	.power_on	= ufs_qcom_phy_power_on,
> +	.power_off	= ufs_qcom_phy_power_off,
> +	.owner		= THIS_MODULE,
> +};
> +
> +static struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
> +	.calibrate_phy		= ufs_qcom_phy_qmp_20nm_phy_calibrate,
> +	.start_serdes		= ufs_qcom_phy_qmp_20nm_start_serdes,
> +	.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_20nm_is_pcs_ready,
> +	.set_tx_lane_enable	= ufs_qcom_phy_qmp_20nm_set_tx_lane_enable,
> +	.power_control		= ufs_qcom_phy_qmp_20nm_power_control,
> +};
> +
> +static int ufs_qcom_phy_qmp_20nm_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct phy *generic_phy;
> +	struct ufs_qcom_phy_qmp_20nm *phy;
> +	int err = 0;
> +
> +	phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
> +	if (!phy) {
> +		dev_err(dev, "%s: failed to allocate phy\n", __func__);
> +		err = -ENOMEM;
> +		goto out;
> +	}
> +
> +	generic_phy = ufs_qcom_phy_generic_probe(pdev, &phy->common_cfg,
> +				&ufs_qcom_phy_qmp_20nm_phy_ops, &phy_20nm_ops);
> +
> +	if (!generic_phy) {
> +		dev_err(dev, "%s: ufs_qcom_phy_generic_probe() failed\n",
> +			__func__);
> +		err = -EIO;
> +		goto out;
> +	}
> +
> +	phy_set_drvdata(generic_phy, phy);
> +
> +	strlcpy(phy->common_cfg.name, UFS_PHY_NAME,
> +			sizeof(phy->common_cfg.name));
> +
> +out:
> +	return err;
> +}
> +
> +static int ufs_qcom_phy_qmp_20nm_remove(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct phy *generic_phy = to_phy(dev);
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +	int err = 0;
> +
> +	err = ufs_qcom_phy_remove(generic_phy, ufs_qcom_phy);
> +	if (err)
> +		dev_err(dev, "%s: ufs_qcom_phy_remove failed = %d\n",
> +			__func__, err);
> +
> +	return err;
> +}
> +
> +static const struct of_device_id ufs_qcom_phy_qmp_20nm_of_match[] = {
> +	{.compatible = "qcom,ufs-phy-qmp-20nm"},

Is there a device tree binding documentation for this compatible string?

Please also run checkpatch for your patches.

Thanks
Kishon

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v7 2/5] phy: qcom-ufs: add support for 20nm phy
@ 2015-01-21  9:32     ` Kishon Vijay Abraham I
  0 siblings, 0 replies; 27+ messages in thread
From: Kishon Vijay Abraham I @ 2015-01-21  9:32 UTC (permalink / raw)
  To: Yaniv Gardi, James.Bottomley, hch
  Cc: linux-kernel, linux-scsi, linux-arm-msm, santoshsy,
	linux-scsi-owner, subhashj, noag, draviv, Grant Likely,
	Rob Herring, open list:OPEN FIRMWARE AND...

hi,

On Thursday 15 January 2015 08:02 PM, Yaniv Gardi wrote:
> This change adds a support for a 20nm qcom-ufs phy that is required in
> platforms that use ufs-qcom controller.
> 
> Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>
> 
> ---
>  drivers/phy/Makefile                |   1 +
>  drivers/phy/phy-qcom-ufs-i.h        |  43 +++++-
>  drivers/phy/phy-qcom-ufs-qmp-20nm.c | 257 ++++++++++++++++++++++++++++++++++++
>  drivers/phy/phy-qcom-ufs-qmp-20nm.h | 235 +++++++++++++++++++++++++++++++++
>  include/linux/phy/phy-qcom-ufs.h    |  59 +++++++++
>  5 files changed, 594 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.c
>  create mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.h
>  create mode 100644 include/linux/phy/phy-qcom-ufs.h
> 
> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
> index 335965d..781b2fa 100644
> --- a/drivers/phy/Makefile
> +++ b/drivers/phy/Makefile
> @@ -35,3 +35,4 @@ obj-$(CONFIG_PHY_XGENE)			+= phy-xgene.o
>  obj-$(CONFIG_PHY_STIH407_USB)		+= phy-stih407-usb.o
>  obj-$(CONFIG_PHY_STIH41X_USB)		+= phy-stih41x-usb.o
>  obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs.o
> +obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs-qmp-20nm.o
> diff --git a/drivers/phy/phy-qcom-ufs-i.h b/drivers/phy/phy-qcom-ufs-i.h
> index dac200f..591a391 100644
> --- a/drivers/phy/phy-qcom-ufs-i.h
> +++ b/drivers/phy/phy-qcom-ufs-i.h
> @@ -15,15 +15,56 @@
>  #ifndef UFS_QCOM_PHY_I_H_
>  #define UFS_QCOM_PHY_I_H_
>  
> +#include <linux/module.h>
>  #include <linux/clk.h>
> +#include <linux/regulator/consumer.h>
>  #include <linux/slab.h>
> -#include <linux/phy/phy.h>
> +#include <linux/phy/phy-qcom-ufs.h>
>  #include <linux/platform_device.h>
>  #include <linux/io.h>
>  #include <linux/delay.h>
>  
> +#define readl_poll_timeout(addr, val, cond, sleep_us, timeout_us) \
> +({ \
> +	ktime_t timeout = ktime_add_us(ktime_get(), timeout_us); \
> +	might_sleep_if(timeout_us); \
> +	for (;;) { \
> +		(val) = readl(addr); \
> +		if (cond) \
> +			break; \
> +		if (timeout_us && ktime_compare(ktime_get(), timeout) > 0) { \
> +			(val) = readl(addr); \
> +			break; \
> +		} \
> +		if (sleep_us) \
> +			usleep_range(DIV_ROUND_UP(sleep_us, 4), sleep_us); \
> +	} \
> +	(cond) ? 0 : -ETIMEDOUT; \
> +})
> +
> +#define UFS_QCOM_PHY_CAL_ENTRY(reg, val)	\
> +	{				\
> +		.reg_offset = reg,	\
> +		.cfg_value = val,	\
> +	}
> +
>  #define UFS_QCOM_PHY_NAME_LEN	30
>  
> +enum {
> +	MASK_SERDES_START       = 0x1,
> +	MASK_PCS_READY          = 0x1,
> +};
> +
> +enum {
> +	OFFSET_SERDES_START     = 0x0,
> +};
> +
> +struct ufs_qcom_phy_stored_attributes {
> +	u32 att;
> +	u32 value;
> +};
> +
> +
>  struct ufs_qcom_phy_calibration {
>  	u32 reg_offset;
>  	u32 cfg_value;
> diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.c b/drivers/phy/phy-qcom-ufs-qmp-20nm.c
> new file mode 100644
> index 0000000..8332f96
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs-qmp-20nm.c
> @@ -0,0 +1,257 @@
> +/*
> + * Copyright (c) 2013-2015, 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 "phy-qcom-ufs-qmp-20nm.h"
> +
> +#define UFS_PHY_NAME "ufs_phy_qmp_20nm"
> +
> +static
> +int ufs_qcom_phy_qmp_20nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
> +					bool is_rate_B)
> +{
> +	struct ufs_qcom_phy_calibration *tbl_A, *tbl_B;
> +	int tbl_size_A, tbl_size_B;
> +	u8 major = ufs_qcom_phy->host_ctrl_rev_major;
> +	u16 minor = ufs_qcom_phy->host_ctrl_rev_minor;
> +	u16 step = ufs_qcom_phy->host_ctrl_rev_step;
> +	int err;
> +
> +	if ((major == 0x1) && (minor == 0x002) && (step == 0x0000)) {
> +		tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_2_0);
> +		tbl_A = phy_cal_table_rate_A_1_2_0;
> +	} else if ((major == 0x1) && (minor == 0x003) && (step == 0x0000)) {
> +		tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_3_0);
> +		tbl_A = phy_cal_table_rate_A_1_3_0;
> +	} else {
> +		dev_err(ufs_qcom_phy->dev, "%s: Unknown UFS-PHY version, no calibration values\n",
> +			__func__);
> +		err = -ENODEV;
> +		goto out;
> +	}
> +
> +	tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
> +	tbl_B = phy_cal_table_rate_B;
> +
> +	err = ufs_qcom_phy_calibrate(ufs_qcom_phy, tbl_A, tbl_size_A,
> +						tbl_B, tbl_size_B, is_rate_B);
> +
> +	if (err)
> +		dev_err(ufs_qcom_phy->dev, "%s: ufs_qcom_phy_calibrate() failed %d\n",
> +			__func__, err);
> +
> +out:
> +	return err;
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_20nm_advertise_quirks(struct ufs_qcom_phy *phy_common)
> +{
> +	phy_common->quirks =
> +		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
> +}
> +
> +static int ufs_qcom_phy_qmp_20nm_init(struct phy *generic_phy)
> +{
> +	struct ufs_qcom_phy_qmp_20nm *phy = phy_get_drvdata(generic_phy);
> +	struct ufs_qcom_phy *phy_common = &phy->common_cfg;
> +	int err = 0;
> +
> +	err = ufs_qcom_phy_init_clks(generic_phy, phy_common);
> +	if (err) {
> +		dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_clks() failed %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	err = ufs_qcom_phy_init_vregulators(generic_phy, phy_common);
> +	if (err) {
> +		dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_vregulators() failed %d\n",
> +			__func__, err);
> +		goto out;
> +	}
> +
> +	ufs_qcom_phy_qmp_20nm_advertise_quirks(phy_common);
> +
> +out:
> +	return err;
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_20nm_power_control(struct ufs_qcom_phy *phy, bool val)
> +{
> +	bool hibern8_exit_after_pwr_collapse = phy->quirks &
> +		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
> +
> +	if (val) {
> +		writel_relaxed(0x1, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
> +		/*
> +		 * Before any transactions involving PHY, ensure PHY knows
> +		 * that it's analog rail is powered ON.
> +		 */
> +		mb();
> +
> +		if (hibern8_exit_after_pwr_collapse) {
> +			/*
> +			 * Give atleast 1us delay after restoring PHY analog
> +			 * power.
> +			 */
> +			usleep_range(1, 2);
> +			writel_relaxed(0x0A, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			writel_relaxed(0x08, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			/*
> +			 * Make sure workaround is deactivated before proceeding
> +			 * with normal PHY operations.
> +			 */
> +			mb();
> +		}
> +	} else {
> +		if (hibern8_exit_after_pwr_collapse) {
> +			writel_relaxed(0x0A, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			writel_relaxed(0x02, phy->mmio +
> +				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
> +			/*
> +			 * Make sure that above workaround is activated before
> +			 * PHY analog power collapse.
> +			 */
> +			mb();
> +		}
> +
> +		writel_relaxed(0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
> +		/*
> +		 * ensure that PHY knows its PHY analog rail is going
> +		 * to be powered down
> +		 */
> +		mb();
> +	}
> +}
> +
> +static
> +void ufs_qcom_phy_qmp_20nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val)
> +{
> +	writel_relaxed(val & UFS_PHY_TX_LANE_ENABLE_MASK,
> +			phy->mmio + UFS_PHY_TX_LANE_ENABLE);
> +	mb();
> +}
> +
> +static inline void ufs_qcom_phy_qmp_20nm_start_serdes(struct ufs_qcom_phy *phy)
> +{
> +	u32 tmp;
> +
> +	tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
> +	tmp &= ~MASK_SERDES_START;
> +	tmp |= (1 << OFFSET_SERDES_START);
> +	writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
> +	mb();
> +}
> +
> +static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
> +{
> +	int err = 0;
> +	u32 val;
> +
> +	err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS,
> +			val, (val & MASK_PCS_READY), 10, 1000000);
> +	if (err)
> +		dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n",
> +			__func__, err);
> +	return err;
> +}
> +
> +static struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
> +	.init		= ufs_qcom_phy_qmp_20nm_init,
> +	.exit		= ufs_qcom_phy_exit,
> +	.power_on	= ufs_qcom_phy_power_on,
> +	.power_off	= ufs_qcom_phy_power_off,
> +	.owner		= THIS_MODULE,
> +};
> +
> +static struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
> +	.calibrate_phy		= ufs_qcom_phy_qmp_20nm_phy_calibrate,
> +	.start_serdes		= ufs_qcom_phy_qmp_20nm_start_serdes,
> +	.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_20nm_is_pcs_ready,
> +	.set_tx_lane_enable	= ufs_qcom_phy_qmp_20nm_set_tx_lane_enable,
> +	.power_control		= ufs_qcom_phy_qmp_20nm_power_control,
> +};
> +
> +static int ufs_qcom_phy_qmp_20nm_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct phy *generic_phy;
> +	struct ufs_qcom_phy_qmp_20nm *phy;
> +	int err = 0;
> +
> +	phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
> +	if (!phy) {
> +		dev_err(dev, "%s: failed to allocate phy\n", __func__);
> +		err = -ENOMEM;
> +		goto out;
> +	}
> +
> +	generic_phy = ufs_qcom_phy_generic_probe(pdev, &phy->common_cfg,
> +				&ufs_qcom_phy_qmp_20nm_phy_ops, &phy_20nm_ops);
> +
> +	if (!generic_phy) {
> +		dev_err(dev, "%s: ufs_qcom_phy_generic_probe() failed\n",
> +			__func__);
> +		err = -EIO;
> +		goto out;
> +	}
> +
> +	phy_set_drvdata(generic_phy, phy);
> +
> +	strlcpy(phy->common_cfg.name, UFS_PHY_NAME,
> +			sizeof(phy->common_cfg.name));
> +
> +out:
> +	return err;
> +}
> +
> +static int ufs_qcom_phy_qmp_20nm_remove(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct phy *generic_phy = to_phy(dev);
> +	struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
> +	int err = 0;
> +
> +	err = ufs_qcom_phy_remove(generic_phy, ufs_qcom_phy);
> +	if (err)
> +		dev_err(dev, "%s: ufs_qcom_phy_remove failed = %d\n",
> +			__func__, err);
> +
> +	return err;
> +}
> +
> +static const struct of_device_id ufs_qcom_phy_qmp_20nm_of_match[] = {
> +	{.compatible = "qcom,ufs-phy-qmp-20nm"},

Is there a device tree binding documentation for this compatible string?

Please also run checkpatch for your patches.

Thanks
Kishon

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v7 5/5] scsi: ufs-qcom-ice: add Inline Crypto Engine (ICE) support for UFS
  2015-01-15 14:32 ` [PATCH v7 5/5] scsi: ufs-qcom-ice: add Inline Crypto Engine (ICE) support for UFS Yaniv Gardi
  2015-01-15 15:21     ` Dov Levenglick
@ 2015-01-23  8:48   ` Paul Bolle
  2015-02-02 14:36     ` ygardi
  1 sibling, 1 reply; 27+ messages in thread
From: Paul Bolle @ 2015-01-23  8:48 UTC (permalink / raw)
  To: Yaniv Gardi
  Cc: Valentin Rothberg, Dov Levenglick, James.Bottomley, hch,
	linux-kernel, linux-scsi, linux-arm-msm, santoshsy,
	linux-scsi-owner, subhashj, noag, draviv, Yaniv Gardi,
	Vinayak Holikatti, James E.J. Bottomley

Yaniv,

On Thu, 2015-01-15 at 16:32 +0200, Yaniv Gardi wrote:
> From: Yaniv Gardi <ygardi@qti.qualcomm.com>
> 
> In-order to enhance storage encryption performance,
> an Inline Cryptographic Engine is introduced to UFS.
> This patch adds in-line encryption capabilities to the UFS
> driver.
> 
> Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>

This patch became commit 8805ccd069b7 ("ufs-qcom-ice: add Inline Crypto
Engine (ICE) support for UFS") in today's linux-next (ie,
next-20150123). I noticed because a script I use to check linux-next
spotted a problem with it.

> ---
>  drivers/scsi/ufs/Kconfig        |  12 +
>  drivers/scsi/ufs/Makefile       |   1 +
>  drivers/scsi/ufs/ufs-qcom-ice.c | 520 ++++++++++++++++++++++++++++++++++++++++
>  drivers/scsi/ufs/ufs-qcom-ice.h | 113 +++++++++
>  drivers/scsi/ufs/ufs-qcom.c     |  56 ++++-
>  drivers/scsi/ufs/ufs-qcom.h     |  25 ++
>  6 files changed, 726 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/scsi/ufs/ufs-qcom-ice.c
>  create mode 100644 drivers/scsi/ufs/ufs-qcom-ice.h
> 
> diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig
> index 8a1f4b3..ecf34ed 100644
> --- a/drivers/scsi/ufs/Kconfig
> +++ b/drivers/scsi/ufs/Kconfig
> @@ -83,3 +83,15 @@ config SCSI_UFS_QCOM
>  
>  	  Select this if you have UFS controller on QCOM chipset.
>  	  If unsure, say N.
> +
> +config SCSI_UFS_QCOM_ICE
> +	bool "QCOM specific hooks to Inline Crypto Engine for UFS driver"
> +	depends on SCSI_UFS_QCOM && CRYPTO_DEV_QCOM_ICE

There's currently no Kconfig symbol CRYPTO_DEV_QCOM_ICE in linux-next.
So SCSI_UFS_QCOM_ICE can not be set and these "in-line encryption
capabilities" can not be enabled.

> +	help
> +	  This selects the QCOM specific additions to support Inline Crypto
> +	  Engine (ICE).
> +	  ICE accelerates the crypto operations and maintains the high UFS
> +	  performance.
> +
> +	  Select this if you have ICE supported for UFS on QCOM chipset.
> +	  If unsure, say N.
> diff --git a/drivers/scsi/ufs/Makefile b/drivers/scsi/ufs/Makefile
> index 8303bcc..31adca5 100644
> --- a/drivers/scsi/ufs/Makefile
> +++ b/drivers/scsi/ufs/Makefile
> @@ -1,5 +1,6 @@
>  # UFSHCD makefile
>  obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-qcom.o
> +obj-$(CONFIG_SCSI_UFS_QCOM_ICE) += ufs-qcom-ice.o
>  obj-$(CONFIG_SCSI_UFSHCD) += ufshcd.o
>  obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o
>  obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o
> diff --git a/drivers/scsi/ufs/ufs-qcom-ice.c b/drivers/scsi/ufs/ufs-qcom-ice.c
> new file mode 100644
> index 0000000..9202b73
> --- /dev/null
> +++ b/drivers/scsi/ufs/ufs-qcom-ice.c
> @@ -0,0 +1,520 @@
> +/* Copyright (c) 2014-2015, 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/of.h>
> +#include <linux/async.h>
> +#include <linux/blkdev.h>
> +#include <crypto/ice.h>

This header is not included in linux-next so manually building
ufs-qcom-ice.o isn't possible either.

I assume a series to add CRYPTO_DEV_QCOM_ICE and crypto/ice.h (and
whatever else is needed to build this) is queued somewhere. Is that
correct?


Paul Bolle

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v7 5/5] scsi: ufs-qcom-ice: add Inline Crypto Engine (ICE) support for UFS
  2015-01-23  8:48   ` Paul Bolle
@ 2015-02-02 14:36     ` ygardi
  0 siblings, 0 replies; 27+ messages in thread
From: ygardi @ 2015-02-02 14:36 UTC (permalink / raw)
  To: Paul Bolle
  Cc: Yaniv Gardi, Valentin Rothberg, Dov Levenglick, james.bottomley,
	hch, linux-kernel, linux-scsi, linux-arm-msm, santoshsy,
	linux-scsi-owner, subhashj, noag, draviv, Yaniv Gardi,
	Vinayak Holikatti, James E.J. Bottomley

Paul,

we have decided to revert the ICE change that support UFS.
a change already uploaded:
look for subject:

[PATCH v1] Revert "scsi: ufs-qcom-ice: add Inline Crypto Engine (ICE)
support for UFS"

thanks,
Yaniv

> Yaniv,
>
> On Thu, 2015-01-15 at 16:32 +0200, Yaniv Gardi wrote:
>> From: Yaniv Gardi <ygardi@qti.qualcomm.com>
>>
>> In-order to enhance storage encryption performance,
>> an Inline Cryptographic Engine is introduced to UFS.
>> This patch adds in-line encryption capabilities to the UFS
>> driver.
>>
>> Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>
>
> This patch became commit 8805ccd069b7 ("ufs-qcom-ice: add Inline Crypto
> Engine (ICE) support for UFS") in today's linux-next (ie,
> next-20150123). I noticed because a script I use to check linux-next
> spotted a problem with it.
>
>> ---
>>  drivers/scsi/ufs/Kconfig        |  12 +
>>  drivers/scsi/ufs/Makefile       |   1 +
>>  drivers/scsi/ufs/ufs-qcom-ice.c | 520
>> ++++++++++++++++++++++++++++++++++++++++
>>  drivers/scsi/ufs/ufs-qcom-ice.h | 113 +++++++++
>>  drivers/scsi/ufs/ufs-qcom.c     |  56 ++++-
>>  drivers/scsi/ufs/ufs-qcom.h     |  25 ++
>>  6 files changed, 726 insertions(+), 1 deletion(-)
>>  create mode 100644 drivers/scsi/ufs/ufs-qcom-ice.c
>>  create mode 100644 drivers/scsi/ufs/ufs-qcom-ice.h
>>
>> diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig
>> index 8a1f4b3..ecf34ed 100644
>> --- a/drivers/scsi/ufs/Kconfig
>> +++ b/drivers/scsi/ufs/Kconfig
>> @@ -83,3 +83,15 @@ config SCSI_UFS_QCOM
>>
>>  	  Select this if you have UFS controller on QCOM chipset.
>>  	  If unsure, say N.
>> +
>> +config SCSI_UFS_QCOM_ICE
>> +	bool "QCOM specific hooks to Inline Crypto Engine for UFS driver"
>> +	depends on SCSI_UFS_QCOM && CRYPTO_DEV_QCOM_ICE
>
> There's currently no Kconfig symbol CRYPTO_DEV_QCOM_ICE in linux-next.
> So SCSI_UFS_QCOM_ICE can not be set and these "in-line encryption
> capabilities" can not be enabled.
>
>> +	help
>> +	  This selects the QCOM specific additions to support Inline Crypto
>> +	  Engine (ICE).
>> +	  ICE accelerates the crypto operations and maintains the high UFS
>> +	  performance.
>> +
>> +	  Select this if you have ICE supported for UFS on QCOM chipset.
>> +	  If unsure, say N.
>> diff --git a/drivers/scsi/ufs/Makefile b/drivers/scsi/ufs/Makefile
>> index 8303bcc..31adca5 100644
>> --- a/drivers/scsi/ufs/Makefile
>> +++ b/drivers/scsi/ufs/Makefile
>> @@ -1,5 +1,6 @@
>>  # UFSHCD makefile
>>  obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-qcom.o
>> +obj-$(CONFIG_SCSI_UFS_QCOM_ICE) += ufs-qcom-ice.o
>>  obj-$(CONFIG_SCSI_UFSHCD) += ufshcd.o
>>  obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o
>>  obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o
>> diff --git a/drivers/scsi/ufs/ufs-qcom-ice.c
>> b/drivers/scsi/ufs/ufs-qcom-ice.c
>> new file mode 100644
>> index 0000000..9202b73
>> --- /dev/null
>> +++ b/drivers/scsi/ufs/ufs-qcom-ice.c
>> @@ -0,0 +1,520 @@
>> +/* Copyright (c) 2014-2015, 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/of.h>
>> +#include <linux/async.h>
>> +#include <linux/blkdev.h>
>> +#include <crypto/ice.h>
>
> This header is not included in linux-next so manually building
> ufs-qcom-ice.o isn't possible either.
>
> I assume a series to add CRYPTO_DEV_QCOM_ICE and crypto/ice.h (and
> whatever else is needed to build this) is queued somewhere. Is that
> correct?
>
>
> Paul Bolle
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>

^ permalink raw reply	[flat|nested] 27+ messages in thread

end of thread, other threads:[~2015-02-02 14:36 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-01-15 14:32 [PATCH v7 0/5] phy: qcom-ufs: add support for QUALCOMM Yaniv Gardi
2015-01-15 14:32 ` [PATCH v7 1/5] phy: qcom-ufs: add support for QUALCOMM Technologies UFS PHY drivers Yaniv Gardi
2015-01-15 14:32   ` Yaniv Gardi
2015-01-15 15:17   ` dovl
2015-01-15 15:17     ` dovl
2015-01-15 14:32 ` [PATCH v7 2/5] phy: qcom-ufs: add support for 20nm phy Yaniv Gardi
2015-01-15 14:32   ` Yaniv Gardi
2015-01-15 15:18   ` dovl
2015-01-15 15:18     ` dovl
2015-01-15 15:18   ` dovl
2015-01-15 15:18     ` dovl
2015-01-21  9:32   ` Kishon Vijay Abraham I
2015-01-21  9:32     ` Kishon Vijay Abraham I
2015-01-15 14:32 ` [PATCH v7 3/5] scsi: ufs-qcom: add support for Qualcomm Technologies Inc platforms Yaniv Gardi
2015-01-15 15:20   ` Dov Levenglick
2015-01-15 15:20     ` Dov Levenglick
2015-01-15 14:32 ` [PATCH v7 4/5] phy: qcom-ufs: add support for 14nm phy Yaniv Gardi
2015-01-15 14:32   ` Yaniv Gardi
2015-01-15 15:18   ` dovl
2015-01-15 15:18     ` dovl
2015-01-15 14:32 ` [PATCH v7 5/5] scsi: ufs-qcom-ice: add Inline Crypto Engine (ICE) support for UFS Yaniv Gardi
2015-01-15 15:21   ` Dov Levenglick
2015-01-15 15:21     ` Dov Levenglick
2015-01-23  8:48   ` Paul Bolle
2015-02-02 14:36     ` ygardi
2015-01-15 15:03 ` [PATCH v7 0/5] phy: qcom-ufs: add support for QUALCOMM Dov Levenglick
2015-01-15 15:03   ` Dov Levenglick

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.