* [PATCH v6 0/4] *** add support for UFS in Qualcomm Technologies
@ 2015-01-11 12:38 Yaniv Gardi
2015-01-11 12:38 ` [PATCH v6 1/4] phy: qcom-ufs: add support for 20nm phy Yaniv Gardi
` (3 more replies)
0 siblings, 4 replies; 7+ messages in thread
From: Yaniv Gardi @ 2015-01-11 12:38 UTC (permalink / raw)
To: James.Bottomley, hch
Cc: linux-kernel, linux-scsi, linux-arm-msm, santoshsy,
linux-scsi-owner, subhashj, ygardi, noag, draviv
*** BLURB HERE ***
Yaniv Gardi (4):
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] 7+ messages in thread
* [PATCH v6 1/4] phy: qcom-ufs: add support for 20nm phy
2015-01-11 12:38 [PATCH v6 0/4] *** add support for UFS in Qualcomm Technologies Yaniv Gardi
@ 2015-01-11 12:38 ` Yaniv Gardi
2015-01-15 8:09 ` Kishon Vijay Abraham I
2015-01-11 12:38 ` [PATCH v6 2/4] scsi: ufs-qcom: add support for Qualcomm Technologies Inc platforms Yaniv Gardi
` (2 subsequent siblings)
3 siblings, 1 reply; 7+ messages in thread
From: Yaniv Gardi @ 2015-01-11 12:38 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/Kconfig | 7 +
drivers/phy/Makefile | 2 +
drivers/phy/phy-qcom-ufs-i.h | 159 ++++++++
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 ++++++++++++++++++++++++++++++++++++
include/linux/phy/phy-qcom-ufs.h | 59 +++
7 files changed, 1464 insertions(+)
create mode 100644 drivers/phy/phy-qcom-ufs-i.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 include/linux/phy/phy-qcom-ufs.h
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..781b2fa 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -34,3 +34,5 @@ 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
+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
new file mode 100644
index 0000000..a175069
--- /dev/null
+++ b/drivers/phy/phy-qcom-ufs-i.h
@@ -0,0 +1,159 @@
+/*
+ * 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/module.h>
+#include <linux/clk.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.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;
+};
+
+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-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/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;
+}
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] 7+ messages in thread
* [PATCH v6 2/4] scsi: ufs-qcom: add support for Qualcomm Technologies Inc platforms
2015-01-11 12:38 [PATCH v6 0/4] *** add support for UFS in Qualcomm Technologies Yaniv Gardi
2015-01-11 12:38 ` [PATCH v6 1/4] phy: qcom-ufs: add support for 20nm phy Yaniv Gardi
@ 2015-01-11 12:38 ` Yaniv Gardi
2015-01-11 12:38 ` [PATCH v6 3/4] phy: qcom-ufs: add support for 14nm phy Yaniv Gardi
2015-01-11 12:38 ` [PATCH v6 4/4] scsi: ufs-qcom-ice: add Inline Crypto Engine (ICE) support for UFS Yaniv Gardi
3 siblings, 0 replies; 7+ messages in thread
From: Yaniv Gardi @ 2015-01-11 12:38 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] 7+ messages in thread
* [PATCH v6 3/4] phy: qcom-ufs: add support for 14nm phy
2015-01-11 12:38 [PATCH v6 0/4] *** add support for UFS in Qualcomm Technologies Yaniv Gardi
2015-01-11 12:38 ` [PATCH v6 1/4] phy: qcom-ufs: add support for 20nm phy Yaniv Gardi
2015-01-11 12:38 ` [PATCH v6 2/4] scsi: ufs-qcom: add support for Qualcomm Technologies Inc platforms Yaniv Gardi
@ 2015-01-11 12:38 ` Yaniv Gardi
2015-01-11 12:38 ` [PATCH v6 4/4] scsi: ufs-qcom-ice: add Inline Crypto Engine (ICE) support for UFS Yaniv Gardi
3 siblings, 0 replies; 7+ messages in thread
From: Yaniv Gardi @ 2015-01-11 12:38 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] 7+ messages in thread
* [PATCH v6 4/4] scsi: ufs-qcom-ice: add Inline Crypto Engine (ICE) support for UFS
2015-01-11 12:38 [PATCH v6 0/4] *** add support for UFS in Qualcomm Technologies Yaniv Gardi
` (2 preceding siblings ...)
2015-01-11 12:38 ` [PATCH v6 3/4] phy: qcom-ufs: add support for 14nm phy Yaniv Gardi
@ 2015-01-11 12:38 ` Yaniv Gardi
3 siblings, 0 replies; 7+ messages in thread
From: Yaniv Gardi @ 2015-01-11 12:38 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] 7+ messages in thread
* Re: [PATCH v6 1/4] phy: qcom-ufs: add support for 20nm phy
2015-01-11 12:38 ` [PATCH v6 1/4] phy: qcom-ufs: add support for 20nm phy Yaniv Gardi
@ 2015-01-15 8:09 ` Kishon Vijay Abraham I
2015-01-15 13:42 ` ygardi
0 siblings, 1 reply; 7+ messages in thread
From: Kishon Vijay Abraham I @ 2015-01-15 8:09 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 Sunday 11 January 2015 06:08 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/Kconfig | 7 +
> drivers/phy/Makefile | 2 +
> drivers/phy/phy-qcom-ufs-i.h | 159 ++++++++
> 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 ++++++++++++++++++++++++++++++++++++
> include/linux/phy/phy-qcom-ufs.h | 59 +++
> 7 files changed, 1464 insertions(+)
> create mode 100644 drivers/phy/phy-qcom-ufs-i.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 include/linux/phy/phy-qcom-ufs.h
I think a single header file should be sufficient here.
It would be helpful if you can split this patch further. First add only the
core ufs layer (Include a documentation on how this layer should be used in
Documentation/phy/) and then use it to add the 14nm and 20nm PHYs.
Thanks
Kishon
>
> 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..781b2fa 100644
> --- a/drivers/phy/Makefile
> +++ b/drivers/phy/Makefile
> @@ -34,3 +34,5 @@ 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
> +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
> new file mode 100644
> index 0000000..a175069
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-ufs-i.h
> @@ -0,0 +1,159 @@
> +/*
> + * 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/module.h>
> +#include <linux/clk.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/slab.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;
> +};
> +
> +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-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/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;
> +}
> 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_ */
>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v6 1/4] phy: qcom-ufs: add support for 20nm phy
2015-01-15 8:09 ` Kishon Vijay Abraham I
@ 2015-01-15 13:42 ` ygardi
0 siblings, 0 replies; 7+ messages in thread
From: ygardi @ 2015-01-15 13:42 UTC (permalink / raw)
To: Kishon Vijay Abraham I
Cc: Yaniv Gardi, james.bottomley, hch, 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 Sunday 11 January 2015 06:08 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/Kconfig | 7 +
>> drivers/phy/Makefile | 2 +
>> drivers/phy/phy-qcom-ufs-i.h | 159 ++++++++
>> 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
>> ++++++++++++++++++++++++++++++++++++
>> include/linux/phy/phy-qcom-ufs.h | 59 +++
>> 7 files changed, 1464 insertions(+)
>> create mode 100644 drivers/phy/phy-qcom-ufs-i.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 include/linux/phy/phy-qcom-ufs.h
>
> I think a single header file should be sufficient here.
>
I believe 2 header files is a better design in this case.
one header file is internal, and should serve internally the phy drivers.
the other one that exposes APIs.
One header file compels us to expose our internal macros, and definitions
in include\linux\phy which is not recommended and not necessary.
the advantage of 2 header files will be more visible if you pick the 14nm
phy code as well. (change#3 in V6, or change#4 in V7)
thanks for your comment.
> It would be helpful if you can split this patch further. First add only
> the
> core ufs layer (Include a documentation on how this layer should be used
> in
> Documentation/phy/) and then use it to add the 14nm and 20nm PHYs.
thanks Kishon for your comment.
I accept the first comment, and in V7, the first change is divided into
2 patches as suggested:
one that adds common support for Qualcomm Technologies UFS Phys
and the other one adds the specific implementation of 20nm phy.
at this point i decided not to add a Documentation, as the
Qualcomm UFS PHY uses the generic PHY framework, and currently we don't
think any further documentation is needed in addition to the Generic Phy
documentation that is presented already in Documentation/phy.txt
thanks for reviewing the change.
>
> Thanks
> Kishon
>>
>> 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..781b2fa 100644
>> --- a/drivers/phy/Makefile
>> +++ b/drivers/phy/Makefile
>> @@ -34,3 +34,5 @@ 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
>> +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
>> new file mode 100644
>> index 0000000..a175069
>> --- /dev/null
>> +++ b/drivers/phy/phy-qcom-ufs-i.h
>> @@ -0,0 +1,159 @@
>> +/*
>> + * 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/module.h>
>> +#include <linux/clk.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/slab.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;
>> +};
>> +
>> +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-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/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;
>> +}
>> 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_ */
>>
>
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2015-01-15 13:42 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-01-11 12:38 [PATCH v6 0/4] *** add support for UFS in Qualcomm Technologies Yaniv Gardi
2015-01-11 12:38 ` [PATCH v6 1/4] phy: qcom-ufs: add support for 20nm phy Yaniv Gardi
2015-01-15 8:09 ` Kishon Vijay Abraham I
2015-01-15 13:42 ` ygardi
2015-01-11 12:38 ` [PATCH v6 2/4] scsi: ufs-qcom: add support for Qualcomm Technologies Inc platforms Yaniv Gardi
2015-01-11 12:38 ` [PATCH v6 3/4] phy: qcom-ufs: add support for 14nm phy Yaniv Gardi
2015-01-11 12:38 ` [PATCH v6 4/4] scsi: ufs-qcom-ice: add Inline Crypto Engine (ICE) support for UFS Yaniv Gardi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).