summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/phy/Makefile3
-rw-r--r--drivers/phy/phy-qcom-ufs-qmp-20nm.c30
-rw-r--r--drivers/phy/phy-qcom-ufs-qmp-20nm.c~HEAD (renamed from drivers/scsi/ufs/ufs-msm-phy-qmp-20nm.c)44
-rw-r--r--drivers/phy/phy-qcom-ufs-qmp-20nm.h37
-rw-r--r--drivers/phy/phy-qcom-ufs-qmp-20nm.h~HEAD (renamed from drivers/scsi/ufs/ufs-msm-phy-qmp-20nm.h)39
-rw-r--r--drivers/phy/phy-qcom-ufs-qmp-28nm.c (renamed from drivers/scsi/ufs/ufs-msm-phy-qmp-28nm.c)4
-rw-r--r--drivers/phy/phy-qcom-ufs-qmp-28nm.h (renamed from drivers/scsi/ufs/ufs-msm-phy-qmp-28nm.h)2
-rw-r--r--drivers/phy/phy-qcom-ufs.c217
-rw-r--r--drivers/phy/phy-qcom-ufs.c~HEAD (renamed from drivers/scsi/ufs/ufs-msm-phy.c)282
-rw-r--r--drivers/scsi/ufs/Makefile3
-rw-r--r--drivers/scsi/ufs/debugfs.c3
-rw-r--r--drivers/scsi/ufs/debugfs.h4
-rw-r--r--drivers/scsi/ufs/ufs-msm-phy.h195
-rw-r--r--drivers/scsi/ufs/ufs-msm.c1205
-rw-r--r--drivers/scsi/ufs/ufs-qcom.c1307
-rw-r--r--drivers/scsi/ufs/ufs-qcom.c~HEAD1576
-rw-r--r--drivers/scsi/ufs/ufs_quirks.c5
-rw-r--r--drivers/scsi/ufs/ufs_test.c4
-rw-r--r--drivers/scsi/ufs/ufshcd-pci.c2
-rw-r--r--drivers/scsi/ufs/ufshcd-pltfrm.c1
-rw-r--r--drivers/scsi/ufs/ufshcd.c23
-rw-r--r--include/linux/phy/phy-qcom-ufs.h6
-rw-r--r--include/linux/scsi/ufs/ufs-qcom.h (renamed from drivers/scsi/ufs/ufs-msm.h)9
-rw-r--r--include/linux/scsi/ufs/ufs.h (renamed from drivers/scsi/ufs/ufs.h)0
-rw-r--r--include/linux/scsi/ufs/ufshcd.h (renamed from drivers/scsi/ufs/ufshcd.h)29
-rw-r--r--include/linux/scsi/ufs/unipro.h (renamed from drivers/scsi/ufs/unipro.h)2
26 files changed, 2456 insertions, 2576 deletions
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 075db1a81aa5..8266d404f310 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -36,6 +36,9 @@ obj-$(CONFIG_PHY_EXYNOS5_USBDRD) += phy-exynos5-usbdrd.o
obj-$(CONFIG_PHY_QCOM_APQ8064_SATA) += phy-qcom-apq8064-sata.o
obj-$(CONFIG_PHY_ROCKCHIP_USB) += phy-rockchip-usb.o
obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o
+obj-$(CONFIG_SCSI_UFS_QCOM) += phy-qcom-ufs.o
+obj-$(CONFIG_SCSI_UFS_QCOM) += phy-qcom-ufs-qmp-28nm.o
+obj-$(CONFIG_SCSI_UFS_QCOM) += phy-qcom-ufs-qmp-20nm.o
obj-$(CONFIG_PHY_ST_SPEAR1310_MIPHY) += phy-spear1310-miphy.o
obj-$(CONFIG_PHY_ST_SPEAR1340_MIPHY) += phy-spear1340-miphy.o
obj-$(CONFIG_PHY_XGENE) += phy-xgene.o
diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.c b/drivers/phy/phy-qcom-ufs-qmp-20nm.c
index b16ea77d07b9..f3e0774a63e3 100644
--- a/drivers/phy/phy-qcom-ufs-qmp-20nm.c
+++ b/drivers/phy/phy-qcom-ufs-qmp-20nm.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
+ * Copyright (c) 2013-2014, 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
@@ -17,40 +17,26 @@
#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)
+int ufs_qcom_phy_qmp_20nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy)
{
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 rate = UFS_QCOM_LIMIT_HS_RATE;
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_A = ARRAY_SIZE(phy_cal_table_rate_A);
+ tbl_A = phy_cal_table_rate_A;
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);
+ tbl_B, tbl_size_B, rate);
if (err)
dev_err(ufs_qcom_phy->dev, "%s: ufs_qcom_phy_calibrate() failed %d\n",
__func__, err);
-out:
return err;
}
@@ -171,7 +157,7 @@ static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
return err;
}
-static const struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
+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,
@@ -179,7 +165,7 @@ static const struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
.owner = THIS_MODULE,
};
-static struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
+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,
diff --git a/drivers/scsi/ufs/ufs-msm-phy-qmp-20nm.c b/drivers/phy/phy-qcom-ufs-qmp-20nm.c~HEAD
index 243bd40dc45e..b16ea77d07b9 100644
--- a/drivers/scsi/ufs/ufs-msm-phy-qmp-20nm.c
+++ b/drivers/phy/phy-qcom-ufs-qmp-20nm.c~HEAD
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013-2014, Linux Foundation. All rights reserved.
+ * 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
@@ -12,42 +12,45 @@
*
*/
-#include <linux/io.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/time.h>
-#include <linux/clk.h>
-#include <linux/of.h>
-#include <linux/iopoll.h>
-#include <linux/platform_device.h>
-#include <linux/msm-bus.h>
-
-#include "ufs-msm-phy.h"
-#include "ufs-msm-phy-qmp-20nm.h"
+#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)
+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;
- int rate = UFS_QCOM_LIMIT_HS_RATE;
+ 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;
- tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A);
- tbl_A = phy_cal_table_rate_A;
+ 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, rate);
+ 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;
}
@@ -168,7 +171,7 @@ static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
return err;
}
-struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
+static const 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,
@@ -176,7 +179,7 @@ struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
.owner = THIS_MODULE,
};
-struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
+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,
@@ -244,7 +247,6 @@ static struct platform_driver ufs_qcom_phy_qmp_20nm_driver = {
.driver = {
.of_match_table = ufs_qcom_phy_qmp_20nm_of_match,
.name = "ufs_qcom_phy_qmp_20nm",
- .owner = THIS_MODULE,
},
};
diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.h b/drivers/phy/phy-qcom-ufs-qmp-20nm.h
index 4f3076bb3d71..3e1f471643f0 100644
--- a/drivers/phy/phy-qcom-ufs-qmp-20nm.h
+++ b/drivers/phy/phy-qcom-ufs-qmp-20nm.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
+ * Copyright (c) 2013-2014, 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
@@ -30,7 +30,6 @@
#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)
@@ -44,8 +43,6 @@
#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)
@@ -101,7 +98,6 @@
#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)
@@ -131,9 +127,8 @@ 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[] = {
+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(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),
@@ -176,28 +171,24 @@ static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_2_0[] = {
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[] = {
+static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
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_START1, 0x98),
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_CMP1, 0x65),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x1e),
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),
@@ -212,24 +203,14 @@ static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_3_0[] = {
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_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_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/scsi/ufs/ufs-msm-phy-qmp-20nm.h b/drivers/phy/phy-qcom-ufs-qmp-20nm.h~HEAD
index e33f6d777384..4f3076bb3d71 100644
--- a/drivers/scsi/ufs/ufs-msm-phy-qmp-20nm.h
+++ b/drivers/phy/phy-qcom-ufs-qmp-20nm.h~HEAD
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013-2014, Linux Foundation. All rights reserved.
+ * 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
@@ -15,7 +15,7 @@
#ifndef UFS_QCOM_PHY_QMP_20NM_H_
#define UFS_QCOM_PHY_QMP_20NM_H_
-#include "ufs-msm-phy.h"
+#include "phy-qcom-ufs-i.h"
/* QCOM UFS PHY control registers */
@@ -30,6 +30,7 @@
#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)
@@ -43,6 +44,8 @@
#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)
@@ -98,6 +101,7 @@
#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)
@@ -127,8 +131,9 @@ struct ufs_qcom_phy_qmp_20nm {
struct ufs_qcom_phy common_cfg;
};
-static struct ufs_qcom_phy_calibration phy_cal_table_rate_A[] = {
+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),
@@ -171,24 +176,28 @@ static struct ufs_qcom_phy_calibration phy_cal_table_rate_A[] = {
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[] = {
+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, 0x98),
+ 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, 0x65),
- UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x1e),
+ 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),
@@ -203,14 +212,24 @@ static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
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_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/scsi/ufs/ufs-msm-phy-qmp-28nm.c b/drivers/phy/phy-qcom-ufs-qmp-28nm.c
index e45532f80567..dd27b4464d3c 100644
--- a/drivers/scsi/ufs/ufs-msm-phy-qmp-28nm.c
+++ b/drivers/phy/phy-qcom-ufs-qmp-28nm.c
@@ -23,8 +23,8 @@
#include <linux/msm-bus.h>
#include <linux/phy/phy.h>
-#include "ufs-msm-phy.h"
-#include "ufs-msm-phy-qmp-28nm.h"
+#include <linux/phy/phy-qcom-ufs.h>
+#include "phy-qcom-ufs-qmp-28nm.h"
#define UFS_PHY_NAME "ufs_qcom_phy_qmp_28nm"
diff --git a/drivers/scsi/ufs/ufs-msm-phy-qmp-28nm.h b/drivers/phy/phy-qcom-ufs-qmp-28nm.h
index 1a8bbc7167c7..b406d99fab3d 100644
--- a/drivers/scsi/ufs/ufs-msm-phy-qmp-28nm.h
+++ b/drivers/phy/phy-qcom-ufs-qmp-28nm.h
@@ -15,7 +15,7 @@
#ifndef UFS_QCOM_PHY_QMP_28NM_H_
#define UFS_QCOM_PHY_QMP_28NM_H_
-#include "ufs-msm-phy.h"
+#include <linux/phy/phy-qcom-ufs.h>
/* QCOM UFS PHY control registers */
diff --git a/drivers/phy/phy-qcom-ufs.c b/drivers/phy/phy-qcom-ufs.c
index 107cb57c3513..5973c67bfd94 100644
--- a/drivers/phy/phy-qcom-ufs.c
+++ b/drivers/phy/phy-qcom-ufs.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
+ * Copyright (c) 2013-2014, 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
@@ -33,7 +33,7 @@ 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 tbl_size_B, int rate)
{
int i;
int ret = 0;
@@ -54,7 +54,7 @@ int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
* with registers of rate B table.
* table.
*/
- if (is_rate_B) {
+ if (rate == PA_HS_MODE_B) {
if (!tbl_B) {
dev_err(ufs_qcom_phy->dev, "%s: tbl_B is NULL",
__func__);
@@ -73,7 +73,6 @@ int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
out:
return ret;
}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_calibrate);
struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
struct ufs_qcom_phy *common_cfg,
@@ -112,7 +111,6 @@ struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
out:
return generic_phy;
}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_generic_probe);
/*
* This assumes the embedded phy structure inside generic_phy is of type
@@ -124,7 +122,6 @@ struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy)
{
return (struct ufs_qcom_phy *)phy_get_drvdata(generic_phy);
}
-EXPORT_SYMBOL_GPL(get_ufs_qcom_phy);
static
int ufs_qcom_phy_base_init(struct platform_device *pdev,
@@ -134,14 +131,21 @@ int ufs_qcom_phy_base_init(struct platform_device *pdev,
struct resource *res;
int err = 0;
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_mem");
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "%s: platform_get_resource() failed. returned NULL\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);
- return err;
+ goto out;
}
/* "dev_ref_clk_ctrl_mem" is optional resource */
@@ -151,11 +155,12 @@ int ufs_qcom_phy_base_init(struct platform_device *pdev,
if (IS_ERR((void const *)phy_common->dev_ref_clk_ctrl_mmio))
phy_common->dev_ref_clk_ctrl_mmio = NULL;
- return 0;
+out:
+ return err;
}
-static int __ufs_qcom_phy_clk_get(struct phy *phy,
- const char *name, struct clk **clk_out, bool err_print)
+int ufs_qcom_phy_clk_get(struct phy *phy,
+ const char *name, struct clk **clk_out)
{
struct clk *clk;
int err = 0;
@@ -165,8 +170,7 @@ static int __ufs_qcom_phy_clk_get(struct phy *phy,
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);
+ dev_err(dev, "failed to get %s err %d", name, err);
} else {
*clk_out = clk;
}
@@ -202,12 +206,10 @@ ufs_qcom_phy_init_clks(struct phy *generic_phy,
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_parent",
+ &phy_common->ref_clk_parent);
+ if (err)
+ goto out;
err = ufs_qcom_phy_clk_get(generic_phy, "ref_clk",
&phy_common->ref_clk);
@@ -215,7 +217,6 @@ ufs_qcom_phy_init_clks(struct phy *generic_phy,
out:
return err;
}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_init_clks);
int
ufs_qcom_phy_init_vregulators(struct phy *generic_phy,
@@ -230,20 +231,12 @@ ufs_qcom_phy_init_vregulators(struct phy *generic_phy,
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;
}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_init_vregulators);
-static int __ufs_qcom_phy_init_vreg(struct phy *phy,
- struct ufs_qcom_phy_vreg *vreg, const char *name, bool optional)
+int ufs_qcom_phy_init_vreg(struct phy *phy,
+ struct ufs_qcom_phy_vreg *vreg, const char *name)
{
int err = 0;
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
@@ -260,9 +253,7 @@ static int __ufs_qcom_phy_init_vreg(struct phy *phy,
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);
+ dev_err(dev, "failed to get %s, %d\n", name, err);
goto out;
}
@@ -282,11 +273,6 @@ static int __ufs_qcom_phy_init_vreg(struct phy *phy,
}
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")) {
@@ -295,9 +281,6 @@ static int __ufs_qcom_phy_init_vreg(struct phy *phy,
} 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:
@@ -401,17 +384,11 @@ int ufs_qcom_phy_enable_ref_clk(struct phy *generic_phy)
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_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);
@@ -425,14 +402,12 @@ int ufs_qcom_phy_enable_ref_clk(struct phy *generic_phy)
goto out;
out_disable_parent:
- if (phy->ref_clk_parent)
- clk_disable_unprepare(phy->ref_clk_parent);
+ clk_disable_unprepare(phy->ref_clk_parent);
out_disable_src:
clk_disable_unprepare(phy->ref_clk_src);
out:
return ret;
}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_enable_ref_clk);
static
int ufs_qcom_phy_disable_vreg(struct phy *phy,
@@ -442,7 +417,7 @@ int ufs_qcom_phy_disable_vreg(struct phy *phy,
struct device *dev = ufs_qcom_phy->dev;
int ret = 0;
- if (!vreg || !vreg->enabled || vreg->is_always_on)
+ if (!vreg || !vreg->enabled)
goto out;
ret = regulator_disable(vreg->reg);
@@ -465,67 +440,27 @@ void ufs_qcom_phy_disable_ref_clk(struct 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_parent);
clk_disable_unprepare(phy->ref_clk_src);
phy->is_ref_clk_enabled = false;
}
}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_disable_ref_clk);
-#define UFS_REF_CLK_EN (1 << 5)
-
-static void ufs_qcom_phy_dev_ref_clk_ctrl(struct phy *generic_phy, bool enable)
+void ufs_qcom_phy_restore_swi_regs(struct phy *generic_phy)
{
+ int i;
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;
+ for (i = 0; i < phy->cached_regs_table_size; i++) {
+ struct ufs_qcom_phy_calibration *table =
+ (struct ufs_qcom_phy_calibration *)phy->cached_regs;
+ writel_relaxed(table[i].cfg_value, phy->mmio +
+ table[i].reg_offset);
}
-}
-
-void ufs_qcom_phy_enable_dev_ref_clk(struct phy *generic_phy)
-{
- ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, true);
-}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_enable_dev_ref_clk);
-void ufs_qcom_phy_disable_dev_ref_clk(struct phy *generic_phy)
-{
- ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, false);
+ /* flush buffered writes */
+ mb();
}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_disable_dev_ref_clk);
/* Turn ON M-PHY RMMI interface clocks */
int ufs_qcom_phy_enable_iface_clk(struct phy *generic_phy)
@@ -554,7 +489,6 @@ int ufs_qcom_phy_enable_iface_clk(struct phy *generic_phy)
out:
return ret;
}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_enable_iface_clk);
/* Turn OFF M-PHY RMMI interface clocks */
void ufs_qcom_phy_disable_iface_clk(struct phy *generic_phy)
@@ -567,7 +501,13 @@ void ufs_qcom_phy_disable_iface_clk(struct phy *generic_phy)
phy->is_iface_clk_enabled = false;
}
}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_disable_iface_clk);
+
+int ufs_qcom_phy_is_cfg_restore_quirk_enabled(struct phy *phy)
+{
+ struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
+
+ return ufs_qcom_phy->quirks & UFS_QCOM_PHY_QUIRK_CFG_RESTORE;
+}
int ufs_qcom_phy_start_serdes(struct phy *generic_phy)
{
@@ -584,7 +524,6 @@ int ufs_qcom_phy_start_serdes(struct phy *generic_phy)
return ret;
}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_start_serdes);
int ufs_qcom_phy_set_tx_lane_enable(struct phy *generic_phy, u32 tx_lanes)
{
@@ -602,7 +541,6 @@ int ufs_qcom_phy_set_tx_lane_enable(struct phy *generic_phy, u32 tx_lanes)
return ret;
}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_set_tx_lane_enable);
void ufs_qcom_phy_save_controller_version(struct phy *generic_phy,
u8 major, u16 minor, u16 step)
@@ -613,9 +551,8 @@ void ufs_qcom_phy_save_controller_version(struct phy *generic_phy,
ufs_qcom_phy->host_ctrl_rev_minor = minor;
ufs_qcom_phy->host_ctrl_rev_step = step;
}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_save_controller_version);
-int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy, bool is_rate_B)
+int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy)
{
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
int ret = 0;
@@ -626,7 +563,7 @@ int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy, bool is_rate_B)
ret = -ENOTSUPP;
} else {
ret = ufs_qcom_phy->phy_spec_ops->
- calibrate_phy(ufs_qcom_phy, is_rate_B);
+ calibrate_phy(ufs_qcom_phy);
if (ret)
dev_err(ufs_qcom_phy->dev, "%s: calibrate_phy() failed %d\n",
__func__, ret);
@@ -634,7 +571,6 @@ int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy, bool is_rate_B)
return ret;
}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_calibrate_phy);
int ufs_qcom_phy_remove(struct phy *generic_phy,
struct ufs_qcom_phy *ufs_qcom_phy)
@@ -646,7 +582,6 @@ int ufs_qcom_phy_remove(struct phy *generic_phy,
return 0;
}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_remove);
int ufs_qcom_phy_exit(struct phy *generic_phy)
{
@@ -657,7 +592,6 @@ int ufs_qcom_phy_exit(struct phy *generic_phy)
return 0;
}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_exit);
int ufs_qcom_phy_is_pcs_ready(struct phy *generic_phy)
{
@@ -672,7 +606,38 @@ int ufs_qcom_phy_is_pcs_ready(struct phy *generic_phy)
return ufs_qcom_phy->phy_spec_ops->
is_physical_coding_sublayer_ready(ufs_qcom_phy);
}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_is_pcs_ready);
+
+int ufs_qcom_phy_save_configuration(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->save_configuration) {
+ dev_err(ufs_qcom_phy->dev, "%s: save_configuration() callback is not supported\n",
+ __func__);
+ ret = -ENOTSUPP;
+ } else {
+ ufs_qcom_phy->phy_spec_ops->save_configuration(ufs_qcom_phy);
+ }
+
+ return ret;
+}
+
+int ufs_qcom_phy_restore_configuration(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->restore_configuration) {
+ dev_err(ufs_qcom_phy->dev, "%s: restore_configuration() callback is not supported\n",
+ __func__);
+ ret = -ENOTSUPP;
+ } else {
+ ufs_qcom_phy->phy_spec_ops->restore_configuration(ufs_qcom_phy);
+ }
+
+ return ret;
+}
int ufs_qcom_phy_power_on(struct phy *generic_phy)
{
@@ -704,22 +669,9 @@ int ufs_qcom_phy_power_on(struct phy *generic_phy)
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:
@@ -727,7 +679,6 @@ out_disable_phy:
out:
return err;
}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_on);
int ufs_qcom_phy_power_off(struct phy *generic_phy)
{
@@ -735,9 +686,6 @@ int ufs_qcom_phy_power_off(struct 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);
@@ -746,4 +694,3 @@ int ufs_qcom_phy_power_off(struct phy *generic_phy)
return 0;
}
-EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_off);
diff --git a/drivers/scsi/ufs/ufs-msm-phy.c b/drivers/phy/phy-qcom-ufs.c~HEAD
index 0cc42095adde..107cb57c3513 100644
--- a/drivers/scsi/ufs/ufs-msm-phy.c
+++ b/drivers/phy/phy-qcom-ufs.c~HEAD
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013-2014, Linux Foundation. All rights reserved.
+ * 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
@@ -12,23 +12,28 @@
*
*/
-#include <linux/io.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/time.h>
-#include <linux/clk.h>
-#include <linux/of.h>
-#include <linux/iopoll.h>
-#include <linux/platform_device.h>
-#include <linux/msm-bus.h>
+#include "phy-qcom-ufs-i.h"
-#include "ufs-msm-phy.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, int rate)
+ int tbl_size_B, bool is_rate_B)
{
int i;
int ret = 0;
@@ -49,7 +54,7 @@ int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
* with registers of rate B table.
* table.
*/
- if (rate == PA_HS_MODE_B) {
+ if (is_rate_B) {
if (!tbl_B) {
dev_err(ufs_qcom_phy->dev, "%s: tbl_B is NULL",
__func__);
@@ -68,10 +73,11 @@ int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
out:
return ret;
}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_calibrate);
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,
+ const struct phy_ops *ufs_qcom_phy_gen_ops,
struct ufs_qcom_phy_specific_ops *phy_spec_ops)
{
int err;
@@ -92,10 +98,11 @@ struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
goto out;
}
- generic_phy = devm_phy_create(dev, NULL, ufs_qcom_phy_gen_ops, NULL);
+ 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);
+ generic_phy = NULL;
goto out;
}
@@ -105,6 +112,7 @@ struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
out:
return generic_phy;
}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_generic_probe);
/*
* This assumes the embedded phy structure inside generic_phy is of type
@@ -116,7 +124,9 @@ struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy)
{
return (struct ufs_qcom_phy *)phy_get_drvdata(generic_phy);
}
+EXPORT_SYMBOL_GPL(get_ufs_qcom_phy);
+static
int ufs_qcom_phy_base_init(struct platform_device *pdev,
struct ufs_qcom_phy *phy_common)
{
@@ -124,26 +134,28 @@ int ufs_qcom_phy_base_init(struct platform_device *pdev,
struct resource *res;
int err = 0;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
- dev_err(dev, "%s: platform_get_resource() failed. returned NULL\n",
- __func__);
- err = -ENOMEM;
- goto out;
- }
-
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_mem");
phy_common->mmio = devm_ioremap_resource(dev, res);
- if (IS_ERR(phy_common->mmio)) {
- err = PTR_ERR(phy_common->mmio);
- dev_err(dev, "ioremap resource failed %d\n", err);
+ 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);
+ return err;
}
-out:
- return err;
+ /* "dev_ref_clk_ctrl_mem" is optional resource */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "dev_ref_clk_ctrl_mem");
+ phy_common->dev_ref_clk_ctrl_mmio = devm_ioremap_resource(dev, res);
+ if (IS_ERR((void const *)phy_common->dev_ref_clk_ctrl_mmio))
+ phy_common->dev_ref_clk_ctrl_mmio = NULL;
+
+ return 0;
}
-int ufs_qcom_phy_clk_get(struct phy *phy,
- const char *name, struct clk **clk_out)
+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;
@@ -153,7 +165,8 @@ int ufs_qcom_phy_clk_get(struct phy *phy,
clk = devm_clk_get(dev, name);
if (IS_ERR(clk)) {
err = PTR_ERR(clk);
- dev_err(dev, "failed to get %s err %d", name, err);
+ if (err_print)
+ dev_err(dev, "failed to get %s err %d", name, err);
} else {
*clk_out = clk;
}
@@ -161,6 +174,13 @@ int ufs_qcom_phy_clk_get(struct phy *phy,
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)
@@ -182,10 +202,12 @@ ufs_qcom_phy_init_clks(struct phy *generic_phy,
if (err)
goto out;
- err = ufs_qcom_phy_clk_get(generic_phy, "ref_clk_parent",
- &phy_common->ref_clk_parent);
- 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);
@@ -193,6 +215,7 @@ ufs_qcom_phy_init_clks(struct phy *generic_phy,
out:
return err;
}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_init_clks);
int
ufs_qcom_phy_init_vregulators(struct phy *generic_phy,
@@ -207,12 +230,20 @@ ufs_qcom_phy_init_vregulators(struct phy *generic_phy,
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;
}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_init_vregulators);
-int ufs_qcom_phy_init_vreg(struct phy *phy,
- struct ufs_qcom_phy_vreg *vreg, const char *name)
+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);
@@ -229,7 +260,9 @@ int ufs_qcom_phy_init_vreg(struct phy *phy,
vreg->reg = devm_regulator_get(dev, name);
if (IS_ERR(vreg->reg)) {
err = PTR_ERR(vreg->reg);
- dev_err(dev, "failed to get %s, %d\n", name, err);
+ vreg->reg = NULL;
+ if (!optional)
+ dev_err(dev, "failed to get %s, %d\n", name, err);
goto out;
}
@@ -249,6 +282,11 @@ int ufs_qcom_phy_init_vreg(struct phy *phy,
}
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")) {
@@ -257,6 +295,9 @@ int ufs_qcom_phy_init_vreg(struct phy *phy,
} 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:
@@ -265,6 +306,13 @@ out:
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)
{
@@ -287,10 +335,10 @@ int ufs_qcom_phy_cfg_vreg(struct phy *phy,
goto out;
}
uA_load = on ? vreg->max_uA : 0;
- ret = regulator_set_optimum_mode(reg, uA_load);
+ ret = regulator_set_load(reg, uA_load);
if (ret >= 0) {
/*
- * regulator_set_optimum_mode() returns new regulator
+ * regulator_set_load() returns new regulator
* mode upon success.
*/
ret = 0;
@@ -304,6 +352,7 @@ out:
return ret;
}
+static
int ufs_qcom_phy_enable_vreg(struct phy *phy,
struct ufs_qcom_phy_vreg *vreg)
{
@@ -352,11 +401,17 @@ int ufs_qcom_phy_enable_ref_clk(struct phy *generic_phy)
goto out;
}
- 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;
+ /*
+ * "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);
@@ -370,13 +425,16 @@ int ufs_qcom_phy_enable_ref_clk(struct phy *generic_phy)
goto out;
out_disable_parent:
- clk_disable_unprepare(phy->ref_clk_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;
}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_enable_ref_clk);
+static
int ufs_qcom_phy_disable_vreg(struct phy *phy,
struct ufs_qcom_phy_vreg *vreg)
{
@@ -384,7 +442,7 @@ int ufs_qcom_phy_disable_vreg(struct phy *phy,
struct device *dev = ufs_qcom_phy->dev;
int ret = 0;
- if (!vreg || !vreg->enabled)
+ if (!vreg || !vreg->enabled || vreg->is_always_on)
goto out;
ret = regulator_disable(vreg->reg);
@@ -407,27 +465,67 @@ void ufs_qcom_phy_disable_ref_clk(struct phy *generic_phy)
if (phy->is_ref_clk_enabled) {
clk_disable_unprepare(phy->ref_clk);
- clk_disable_unprepare(phy->ref_clk_parent);
+ /*
+ * "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;
}
}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_disable_ref_clk);
+
+#define UFS_REF_CLK_EN (1 << 5)
-void ufs_qcom_phy_restore_swi_regs(struct phy *generic_phy)
+static void ufs_qcom_phy_dev_ref_clk_ctrl(struct phy *generic_phy, bool enable)
{
- int i;
struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
- for (i = 0; i < phy->cached_regs_table_size; i++) {
- struct ufs_qcom_phy_calibration *table =
- (struct ufs_qcom_phy_calibration *)phy->cached_regs;
- writel_relaxed(table[i].cfg_value, phy->mmio +
- table[i].reg_offset);
+ 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;
}
+}
- /* flush buffered writes */
- mb();
+void ufs_qcom_phy_enable_dev_ref_clk(struct phy *generic_phy)
+{
+ ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, true);
+}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_enable_dev_ref_clk);
+
+void ufs_qcom_phy_disable_dev_ref_clk(struct phy *generic_phy)
+{
+ ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, false);
}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_disable_dev_ref_clk);
/* Turn ON M-PHY RMMI interface clocks */
int ufs_qcom_phy_enable_iface_clk(struct phy *generic_phy)
@@ -456,6 +554,7 @@ int ufs_qcom_phy_enable_iface_clk(struct phy *generic_phy)
out:
return ret;
}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_enable_iface_clk);
/* Turn OFF M-PHY RMMI interface clocks */
void ufs_qcom_phy_disable_iface_clk(struct phy *generic_phy)
@@ -468,13 +567,7 @@ void ufs_qcom_phy_disable_iface_clk(struct phy *generic_phy)
phy->is_iface_clk_enabled = false;
}
}
-
-int ufs_qcom_phy_is_cfg_restore_quirk_enabled(struct phy *phy)
-{
- struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
-
- return ufs_qcom_phy->quirks & UFS_QCOM_PHY_QUIRK_CFG_RESTORE;
-}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_disable_iface_clk);
int ufs_qcom_phy_start_serdes(struct phy *generic_phy)
{
@@ -491,6 +584,7 @@ int ufs_qcom_phy_start_serdes(struct phy *generic_phy)
return ret;
}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_start_serdes);
int ufs_qcom_phy_set_tx_lane_enable(struct phy *generic_phy, u32 tx_lanes)
{
@@ -508,6 +602,7 @@ int ufs_qcom_phy_set_tx_lane_enable(struct phy *generic_phy, u32 tx_lanes)
return ret;
}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_set_tx_lane_enable);
void ufs_qcom_phy_save_controller_version(struct phy *generic_phy,
u8 major, u16 minor, u16 step)
@@ -518,8 +613,9 @@ void ufs_qcom_phy_save_controller_version(struct phy *generic_phy,
ufs_qcom_phy->host_ctrl_rev_minor = minor;
ufs_qcom_phy->host_ctrl_rev_step = step;
}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_save_controller_version);
-int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy)
+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;
@@ -530,7 +626,7 @@ int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy)
ret = -ENOTSUPP;
} else {
ret = ufs_qcom_phy->phy_spec_ops->
- calibrate_phy(ufs_qcom_phy);
+ calibrate_phy(ufs_qcom_phy, is_rate_B);
if (ret)
dev_err(ufs_qcom_phy->dev, "%s: calibrate_phy() failed %d\n",
__func__, ret);
@@ -538,6 +634,7 @@ int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy)
return ret;
}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_calibrate_phy);
int ufs_qcom_phy_remove(struct phy *generic_phy,
struct ufs_qcom_phy *ufs_qcom_phy)
@@ -549,6 +646,7 @@ int ufs_qcom_phy_remove(struct phy *generic_phy,
return 0;
}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_remove);
int ufs_qcom_phy_exit(struct phy *generic_phy)
{
@@ -559,6 +657,7 @@ int ufs_qcom_phy_exit(struct phy *generic_phy)
return 0;
}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_exit);
int ufs_qcom_phy_is_pcs_ready(struct phy *generic_phy)
{
@@ -573,38 +672,7 @@ int ufs_qcom_phy_is_pcs_ready(struct phy *generic_phy)
return ufs_qcom_phy->phy_spec_ops->
is_physical_coding_sublayer_ready(ufs_qcom_phy);
}
-
-int ufs_qcom_phy_save_configuration(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->save_configuration) {
- dev_err(ufs_qcom_phy->dev, "%s: save_configuration() callback is not supported\n",
- __func__);
- ret = -ENOTSUPP;
- } else {
- ufs_qcom_phy->phy_spec_ops->save_configuration(ufs_qcom_phy);
- }
-
- return ret;
-}
-
-int ufs_qcom_phy_restore_configuration(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->restore_configuration) {
- dev_err(ufs_qcom_phy->dev, "%s: restore_configuration() callback is not supported\n",
- __func__);
- ret = -ENOTSUPP;
- } else {
- ufs_qcom_phy->phy_spec_ops->restore_configuration(ufs_qcom_phy);
- }
-
- return ret;
-}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_is_pcs_ready);
int ufs_qcom_phy_power_on(struct phy *generic_phy)
{
@@ -636,9 +704,22 @@ int ufs_qcom_phy_power_on(struct phy *generic_phy)
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:
@@ -646,6 +727,7 @@ out_disable_phy:
out:
return err;
}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_on);
int ufs_qcom_phy_power_off(struct phy *generic_phy)
{
@@ -653,6 +735,9 @@ int ufs_qcom_phy_power_off(struct 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);
@@ -661,3 +746,4 @@ int ufs_qcom_phy_power_off(struct phy *generic_phy)
return 0;
}
+EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_off);
diff --git a/drivers/scsi/ufs/Makefile b/drivers/scsi/ufs/Makefile
index 4472d91b05c6..876877e602f5 100644
--- a/drivers/scsi/ufs/Makefile
+++ b/drivers/scsi/ufs/Makefile
@@ -1,8 +1,5 @@
# UFSHCD makefile
obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-qcom.o
-obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-msm-phy.o
-obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-msm-phy-qmp-28nm.o
-obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-msm-phy-qmp-20nm.o
obj-$(CONFIG_SCSI_UFSHCD) += ufshcd.o ufs_quirks.o
obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o
obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o
diff --git a/drivers/scsi/ufs/debugfs.c b/drivers/scsi/ufs/debugfs.c
index 1a206f645b09..8790f7a80682 100644
--- a/drivers/scsi/ufs/debugfs.c
+++ b/drivers/scsi/ufs/debugfs.c
@@ -19,7 +19,8 @@
#include <linux/random.h>
#include "debugfs.h"
-#include "unipro.h"
+#include <linux/scsi/ufs/unipro.h>
+#include "ufshci.h"
enum field_width {
BYTE = 1,
diff --git a/drivers/scsi/ufs/debugfs.h b/drivers/scsi/ufs/debugfs.h
index 17877cfe9bdb..0c481310921c 100644
--- a/drivers/scsi/ufs/debugfs.h
+++ b/drivers/scsi/ufs/debugfs.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2013-2014, 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
@@ -21,7 +21,7 @@
#define _UFS_DEBUGFS_H
#include <linux/debugfs.h>
-#include "ufshcd.h"
+#include <linux/scsi/ufs/ufshcd.h>
#ifdef CONFIG_DEBUG_FS
void ufsdbg_add_debugfs(struct ufs_hba *hba);
diff --git a/drivers/scsi/ufs/ufs-msm-phy.h b/drivers/scsi/ufs/ufs-msm-phy.h
deleted file mode 100644
index d356b0ec31e4..000000000000
--- a/drivers/scsi/ufs/ufs-msm-phy.h
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (c) 2013-2014, 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_H_
-#define UFS_QCOM_PHY_H_
-
-#include <linux/io.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/time.h>
-#include <linux/clk.h>
-#include <linux/of.h>
-#include <linux/iopoll.h>
-#include <linux/platform_device.h>
-#include <linux/phy/phy.h>
-#include <linux/msm-bus.h>
-
-#include "ufshcd.h"
-#include "unipro.h"
-#include "ufs-msm.h"
-
-#define UFS_QCOM_PHY_CAL_ENTRY(reg, val) \
- { \
- .reg_offset = reg, \
- .cfg_value = val, \
- }
-
-#define UFS_QCOM_PHY_NAME_LEN 30
-
-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 {
- struct list_head list;
- struct device *dev;
- void __iomem *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;
- struct ufs_qcom_phy_vreg vdda_pll;
- struct ufs_qcom_phy_vreg vdda_phy;
- unsigned int quirks;
- u8 host_ctrl_rev_major;
- u16 host_ctrl_rev_minor;
- u16 host_ctrl_rev_step;
-
- /*
- * As part of UFS power management, UFS link would be put in hibernate
- * and UFS device would be put in SLEEP mode as part of runtime/system
- * suspend callback. But when system goes into suspend with VDD
- * minimization, UFS PHY states are being reset which means UFS link
- * hibernate exit command on system resume would fail.
- * If this quirk is enabled then above issue is workaround by saving
- * the UFS PHY state information before system goes into suspend and
- * restoring the saved state information during system resume but
- * before executing the hibern8 exit command.
- * Note that this quirk will help restoring the PHY state if even when
- * link in not kept in hibern8 during suspend.
- *
- * Here is the list of steps to save/restore the configuration:
- * Before entering into system suspend:
- * 1. Read Critical PCS SWI Registers + less critical PHY CSR
- * 2. Read RMMI Attributes
- * Enter into system suspend
- * After exiting from system suspend:
- * 1. Set UFS_PHY_SOFT_RESET bit in UFS_CFG1 register of the UFS
- * Controller
- * 2. Write 0x01 to the UFS_PHY_POWER_DOWN_CONTROL register in the
- * UFS PHY
- * 3. Write back the values of the PHY SWI registers
- * 4. Clear UFS_PHY_SOFT_RESET bit in UFS_CFG1 register of the UFS
- * Controller
- * 5. Write 0x01 to the UFS_PHY_PHY_START in the UFS PHY. This will
- * start the PLL calibration and bring-up of the PHY.
- * 6. Write back the values to the PHY RMMI Attributes
- * 7. Wait for UFS_PHY_PCS_READY_STATUS[0] to be '1'
- */
- #define UFS_QCOM_PHY_QUIRK_CFG_RESTORE (1 << 0)
-
- /*
- * If UFS PHY power down is deasserted and power is restored to analog
- * circuits, the rx_sigdet can glitch. If the glitch is wide enough,
- * it can trigger the digital logic to think it saw a DIF-N and cause
- * it to exit Hibern8. Disabling the rx_sigdet during power-up masks
- * the glitch.
- */
- #define UFS_QCOM_PHY_DIS_SIGDET_BEFORE_PWR_COLLAPSE (1 << 1)
-
- /*
- * 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 (1 << 2)
-
- 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
- * @save_configuration: pointer to a function that saves phy
- * configuration
- * @is_physical_coding_sublayer_ready: pointer to a function that
- * checks pcs readiness
- * @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);
- void (*start_serdes) (struct ufs_qcom_phy *phy);
- void (*save_configuration)(struct ufs_qcom_phy *phy);
- void (*restore_configuration)(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);
-};
-
-int ufs_qcom_phy_init_vreg(struct phy *phy,
- struct ufs_qcom_phy_vreg *vreg, const char *name);
-int ufs_qcom_phy_cfg_vreg(struct phy *phy,
- struct ufs_qcom_phy_vreg *vreg, bool on);
-int ufs_qcom_phy_enable_vreg(struct phy *phy,
- struct ufs_qcom_phy_vreg *vreg);
-int ufs_qcom_phy_disable_vreg(struct phy *phy,
- struct ufs_qcom_phy_vreg *vreg);
-int ufs_qcom_phy_enable_ref_clk(struct phy *phy);
-void ufs_qcom_phy_disable_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);
-void ufs_qcom_phy_restore_swi_regs(struct phy *phy);
-int ufs_qcom_phy_link_startup_post_change(struct phy *phy,
- struct ufs_hba *hba);
-int ufs_qcom_phy_base_init(struct platform_device *pdev,
- struct ufs_qcom_phy *ufs_qcom_phy_ops);
-int ufs_qcom_phy_is_cfg_restore_quirk_enabled(struct phy *phy);
-struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy);
-int ufs_qcom_phy_start_serdes(struct phy *generic_phy);
-int ufs_qcom_phy_set_tx_lane_enable(struct phy *generic_phy, u32 tx_lanes);
-int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy);
-int ufs_qcom_phy_is_pcs_ready(struct phy *generic_phy);
-int ufs_qcom_phy_save_configuration(struct phy *generic_phy);
-int ufs_qcom_phy_restore_configuration(struct phy *generic_phy);
-void ufs_qcom_phy_save_controller_version(struct phy *generic_phy,
- u8 major, u16 minor, u16 step);
-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,
- int rate);
-#endif
diff --git a/drivers/scsi/ufs/ufs-msm.c b/drivers/scsi/ufs/ufs-msm.c
deleted file mode 100644
index cedfed39454c..000000000000
--- a/drivers/scsi/ufs/ufs-msm.c
+++ /dev/null
@@ -1,1205 +0,0 @@
-/*
- * Copyright (c) 2013-2014, 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/io.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/time.h>
-#include <linux/clk.h>
-#include <linux/of.h>
-#include <linux/iopoll.h>
-#include <linux/platform_device.h>
-
-#include <linux/msm-bus.h>
-#include <soc/qcom/scm.h>
-
-#include "ufshcd.h"
-#include "ufs-msm.h"
-#include "ufs-msm-phy.h"
-
-static int 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_update_sec_cfg(struct ufs_hba *hba, bool restore_sec_cfg);
-
-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,
- "rx_lane1_sync_clk", host->rx_l1_sync_clk);
- if (err)
- goto disable_rx_l0;
-
- err = ufs_qcom_host_clk_enable(dev,
- "tx_lane0_sync_clk", host->tx_l0_sync_clk);
- if (err)
- goto disable_rx_l1;
-
- err = ufs_qcom_host_clk_enable(dev,
- "tx_lane1_sync_clk", host->tx_l1_sync_clk);
- if (err)
- goto disable_tx_l0;
-
- host->is_lane_clks_enabled = true;
- goto out;
-
-disable_tx_l0:
- clk_disable_unprepare(host->tx_l0_sync_clk);
-disable_rx_l1:
- clk_disable_unprepare(host->rx_l1_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,
- "rx_lane1_sync_clk", &host->rx_l1_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,
- "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;
-
- /* 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);
- 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;
-}
-
-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);
- 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);
-
- 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)) {
- if (ufs_qcom_phy_is_cfg_restore_quirk_enabled(phy) &&
- ufs_qcom_is_link_hibern8(hba)) {
- ret = ufs_qcom_phy_save_configuration(phy);
- if (ret)
- dev_err(hba->dev, "%s: failed ufs_qcom_phy_save_configuration %d\n",
- __func__, ret);
- }
- phy_power_off(phy);
- }
-
-out:
- return ret;
-}
-
-static bool ufs_qcom_is_phy_config_restore_required(struct ufs_hba *hba)
-{
- struct ufs_qcom_host *host = hba->priv;
- struct phy *phy = host->generic_phy;
-
- return ufs_qcom_phy_is_cfg_restore_quirk_enabled(phy)
- && ufshcd_is_link_hibern8(hba)
- && hba->is_sys_suspended;
-}
-
-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;
-
- if (ufs_qcom_is_phy_config_restore_required(hba)) {
- ufs_qcom_assert_reset(hba);
- /* provide 1ms delay to let the reset pulse propagate */
- usleep_range(1000, 1100);
- }
-
- err = phy_power_on(phy);
- if (err) {
- dev_err(hba->dev, "%s: failed enabling regs, err = %d\n",
- __func__, err);
- goto out;
- }
-
- if (ufs_qcom_is_phy_config_restore_required(hba)) {
- ufs_qcom_phy_restore_swi_regs(phy);
-
- /* 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);
-
- err = ufs_qcom_phy_start_serdes(phy);
- if (err) {
- dev_err(hba->dev, "%s: ufs_qcom_phy_start_serdes() failed, err = %d\n",
- __func__, err);
- goto out;
- }
-
- err = ufs_qcom_phy_restore_configuration(phy);
- if (err) {
- dev_err(hba->dev, "%s: ufs_qcom_phy_restore_configuration() failed, err = %d\n",
- __func__, err);
- goto out;
- }
-
- err = ufs_qcom_phy_is_pcs_ready(phy);
- if (err) {
- dev_err(hba->dev, "%s: is_physical_coding_sublayer_ready() failed, 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;
-};
-
-/**
- * as every power mode, according to the UFS spec, have a defined
- * number that are not corresponed to their order or power
- * consumption (i.e 5, 2, 4, 1 respectively from low to high),
- * we need to map them into array, so we can scan it easily
- * in order to find the minimum required power mode.
- * also, we can use this routine to go the other way around,
- * and from array index, the fetch the correspond power mode.
- */
-static int map_unmap_pwr_mode(u32 mode, bool is_pwr_to_arr)
-{
- enum {SL_MD = 0, SLA_MD = 1, FS_MD = 2, FSA_MD = 3, UNDEF = 4};
- int ret = -EINVAL;
-
- if (is_pwr_to_arr) {
- switch (mode) {
- case SLOW_MODE:
- ret = SL_MD;
- break;
- case SLOWAUTO_MODE:
- ret = SLA_MD;
- break;
- case FAST_MODE:
- ret = FS_MD;
- break;
- case FASTAUTO_MODE:
- ret = FSA_MD;
- break;
- default:
- ret = UNDEF;
- break;
- }
- } else {
- switch (mode) {
- case SL_MD:
- ret = SLOW_MODE;
- break;
- case SLA_MD:
- ret = SLOWAUTO_MODE;
- break;
- case FS_MD:
- ret = FAST_MODE;
- break;
- case FSA_MD:
- ret = FASTAUTO_MODE;
- break;
- default:
- ret = -EINVAL;
- break;
- }
- }
-
- return ret;
-}
-
-#define NUM_OF_SUPPORTED_MODES 5
-static int get_pwr_dev_param(struct ufs_qcom_dev_params *qcom_param,
- struct ufs_pa_layer_attr *dev_max,
- struct ufs_pa_layer_attr *dev_req)
-{
- int arr[NUM_OF_SUPPORTED_MODES] = {0};
- int i;
- int min_power;
- int min_qcom_gear;
- int min_dev_gear;
- bool is_max_dev_hs;
- bool is_max_qcom_hs;
-
- /**
- * mapping the max. supported power mode of the device
- * and the max. pre-defined support power mode of the vendor
- * in order to scan them easily
- */
- arr[map_unmap_pwr_mode(dev_max->pwr_rx, true)]++;
- arr[map_unmap_pwr_mode(dev_max->pwr_tx, true)]++;
-
- if (qcom_param->desired_working_mode == SLOW) {
- arr[map_unmap_pwr_mode(qcom_param->rx_pwr_pwm, true)]++;
- arr[map_unmap_pwr_mode(qcom_param->tx_pwr_pwm, true)]++;
- } else {
- arr[map_unmap_pwr_mode(qcom_param->rx_pwr_hs, true)]++;
- arr[map_unmap_pwr_mode(qcom_param->tx_pwr_hs, true)]++;
- }
-
- for (i = 0; i < NUM_OF_SUPPORTED_MODES; ++i) {
- if (arr[i] != 0)
- break;
- }
-
- /* no supported power mode found */
- if (i == NUM_OF_SUPPORTED_MODES) {
- return -EINVAL;
- } else {
- min_power = map_unmap_pwr_mode(i, false);
- if (min_power >= 0)
- dev_req->pwr_rx = dev_req->pwr_tx = min_power;
- else
- return -EINVAL;
- }
-
- /**
- * 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.
- */
- dev_req->lane_tx = min_t(u32, dev_max->lane_tx, qcom_param->tx_lanes);
- dev_req->lane_rx = min_t(u32, dev_max->lane_rx, qcom_param->rx_lanes);
-
- if (dev_max->pwr_rx == SLOW_MODE ||
- dev_max->pwr_rx == SLOWAUTO_MODE)
- is_max_dev_hs = false;
- else
- is_max_dev_hs = true;
-
- /* setting the device maximum gear */
- min_dev_gear = min_t(u32, dev_max->gear_rx, dev_max->gear_tx);
-
- /**
- * setting the desired gear to be the minimum according to the desired
- * power mode
- */
- if (qcom_param->desired_working_mode == SLOW) {
- is_max_qcom_hs = false;
- min_qcom_gear = min_t(u32, qcom_param->pwm_rx_gear,
- qcom_param->pwm_tx_gear);
- } else {
- is_max_qcom_hs = true;
- min_qcom_gear = min_t(u32, qcom_param->hs_rx_gear,
- qcom_param->hs_tx_gear);
- }
-
- /**
- * 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 the gear, as he is the one that also decided previously what
- * pwr the device will be configured to.
- */
- if ((is_max_dev_hs && is_max_qcom_hs) ||
- (!is_max_dev_hs && !is_max_qcom_hs)) {
- dev_req->gear_rx = dev_req->gear_tx =
- min_t(u32, min_dev_gear, min_qcom_gear);
- } else if (!is_max_dev_hs) {
- dev_req->gear_rx = dev_req->gear_tx = min_dev_gear;
- } else {
- dev_req->gear_rx = dev_req->gear_tx = min_qcom_gear;
- }
-
- dev_req->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];
-
- err = ufs_qcom_get_speed_mode(&host->dev_req_params, mode);
- if (err)
- goto out;
-
- vote = ufs_qcom_get_bus_vote(host, mode);
- if (vote >= 0)
- err = ufs_qcom_set_bus_vote(host, vote);
- else
- err = vote;
-
-out:
- 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 = 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);
-
- if ((major == 0x1) && (minor == 0x001) && (step == 0x0001))
- hba->quirks |= (UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS
- | UFSHCD_QUIRK_BROKEN_INTR_AGGR
- | UFSHCD_QUIRK_BROKEN_PA_RXHSUNTERMCAP
- | UFSHCD_QUIRK_BROKEN_LCC);
- else if ((major == 0x1) && (minor == 0x002) && (step == 0x0000))
- hba->quirks |= (UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS
- | UFSHCD_QUIRK_BROKEN_PA_RXHSUNTERMCAP
- | UFSHCD_QUIRK_BROKEN_LCC);
-}
-
-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) {
- err = msm_bus_scale_client_update_request(
- host->bus_vote.client_handle, vote);
- if (err) {
- dev_err(host->hba->dev,
- "%s: msm_bus_scale_client_update_request() failed: bus_client_handle=0x%x, vote=%d, err=%d\n",
- __func__, host->bus_vote.client_handle,
- vote, err);
- goto out;
- }
-
- host->bus_vote.curr_vote = vote;
- }
-out:
- return err;
-}
-
-static int ufs_qcom_get_speed_mode(struct ufs_pa_layer_attr *p, char *result)
-{
- int err = 0;
- int gear = max_t(u32, p->gear_rx, p->gear_tx);
- int lanes = max_t(u32, p->lane_rx, p->lane_tx);
- int pwr = max_t(u32, map_unmap_pwr_mode(p->pwr_rx, true),
- map_unmap_pwr_mode(p->pwr_tx, true));
-
- /* 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 = 0;
-
- pwr = map_unmap_pwr_mode(pwr, false);
- if (pwr < 0) {
- err = pwr;
- goto out;
- }
-
- if (pwr == FAST_MODE || pwr == FASTAUTO_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
- snprintf(result, BUS_VECTOR_NAME_LEN, "%s_G%d_L%d",
- "PWM", gear, lanes);
-out:
- return err;
-}
-
-
-
-static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on)
-{
- struct ufs_qcom_host *host = hba->priv;
- int err;
- 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;
-
- 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);
- 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 msm_bus_scale_pdata *bus_pdata;
- struct device *dev = host->hba->dev;
- struct platform_device *pdev = to_platform_device(dev);
- struct device_node *np = dev->of_node;
-
- bus_pdata = msm_bus_cl_get_pdata(pdev);
- if (!bus_pdata) {
- dev_err(dev, "%s: failed to get bus vectors\n", __func__);
- err = -ENODATA;
- goto out;
- }
-
- err = of_property_count_strings(np, "qcom,bus-vector-names");
- if (err < 0 || err != bus_pdata->num_usecases) {
- dev_err(dev, "%s: qcom,bus-vector-names not specified correctly %d\n",
- __func__, err);
- goto out;
- }
-
- host->bus_vote.client_handle = msm_bus_scale_register_client(bus_pdata);
- if (!host->bus_vote.client_handle) {
- dev_err(dev, "%s: msm_bus_scale_register_client failed\n",
- __func__);
- err = -EFAULT;
- 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;
- host->generic_phy = devm_phy_get(dev, "ufsphy");
-
- if (IS_ERR(host->generic_phy)) {
- err = PTR_ERR(host->generic_phy);
- dev_err(dev, "PHY get failed %d\n", err);
- goto out;
- }
-
- hba->priv = (void *)host;
-
- /* restore the secure configuration */
- ufs_qcom_update_sec_cfg(hba, true);
-
- 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_HIBERN8_WITH_CLK_GATING;
- hba->caps |= UFSHCD_CAP_AUTO_BKOPS_SUSPEND;
- ufs_qcom_setup_clocks(hba, true);
- goto out;
-
-out_disable_phy:
- phy_power_off(host->generic_phy);
-out_unregister_bus:
- phy_exit(host->generic_phy);
- msm_bus_scale_unregister_client(host->bus_vote.client_handle);
-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;
-
- msm_bus_scale_unregister_client(host->bus_vote.client_handle);
- ufs_qcom_disable_lane_clks(host);
- phy_power_off(host->generic_phy);
-}
-
-
-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);
- ufs_qcom_update_bus_bw_vote(host);
-}
-
-/*
- * This function should be called to restore the security configuration of UFS
- * register space after coming out of UFS host core power collapse.
- *
- * @hba: host controller instance
- * @restore_sec_cfg: Set "true" if secure configuration needs to be restored
- * and set "false" when secure configuration is lost.
- */
-static int ufs_qcom_update_sec_cfg(struct ufs_hba *hba, bool restore_sec_cfg)
-{
- int ret = 0, scm_ret = 0;
- struct ufs_qcom_host *host = hba->priv;
-
- /* scm command buffer structrue */
- struct msm_scm_cmd_buf {
- unsigned int device_id;
- unsigned int spare;
- } cbuf = {0};
- #define RESTORE_SEC_CFG_CMD 0x2
- #define UFS_TZ_DEV_ID 19
-
- if (!host || !hba->vreg_info.vdd_hba ||
- !(host->sec_cfg_updated ^ restore_sec_cfg)) {
- return 0;
- } else if (!restore_sec_cfg) {
- /*
- * Clear the flag so next time when this function is called
- * with restore_sec_cfg set to true, we can restore the secure
- * configuration.
- */
- host->sec_cfg_updated = false;
- goto out;
- } else if (hba->clk_gating.state != CLKS_ON) {
- /*
- * Clocks should be ON to restore the host controller secure
- * configuration.
- */
- goto out;
- }
-
- /*
- * If we are here, Host controller clocks are running, Host controller
- * power collapse feature is supported and Host controller has just came
- * out of power collapse.
- */
- cbuf.device_id = UFS_TZ_DEV_ID;
- ret = scm_restore_sec_cfg(cbuf.device_id, cbuf.spare, &scm_ret);
- if (ret || scm_ret) {
- dev_err(hba->dev, "%s: failed, ret %d scm_ret %d\n",
- __func__, ret, scm_ret);
- if (!ret)
- ret = scm_ret;
- } else {
- host->sec_cfg_updated = true;
- }
-
-out:
- dev_dbg(hba->dev, "%s: ip: restore_sec_cfg %d, op: restore_sec_cfg %d, ret %d scm_ret %d\n",
- __func__, restore_sec_cfg, host->sec_cfg_updated, ret, scm_ret);
- return ret;
-}
-
-/**
- * struct ufs_hba_qcom_vops - UFS QCOM specific variant operations
- *
- * The variant operations configure the necessary controller and PHY
- * handshake during initializaiton.
- */
-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,
- .update_sec_cfg = ufs_qcom_update_sec_cfg,
-};
-EXPORT_SYMBOL(ufs_hba_qcom_vops);
diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c
index 4f38d008bfb4..e3365ae2a700 100644
--- a/drivers/scsi/ufs/ufs-qcom.c
+++ b/drivers/scsi/ufs/ufs-qcom.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
+ * Copyright (c) 2013-2014, 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
@@ -12,51 +12,32 @@
*
*/
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
#include <linux/time.h>
+#include <linux/clk.h>
#include <linux/of.h>
+#include <linux/iopoll.h>
#include <linux/platform_device.h>
-#include <linux/phy/phy.h>
+#include <linux/msm-bus.h>
+#include <soc/qcom/scm.h>
+
+#include <linux/scsi/ufs/ufshcd.h>
+#include <linux/scsi/ufs/ufs-qcom.h>
#include <linux/phy/phy-qcom-ufs.h>
#include "ufshcd.h"
#include "ufshcd-pltfrm.h"
#include "unipro.h"
#include "ufs-qcom.h"
#include "ufshci.h"
-#define UFS_QCOM_DEFAULT_DBG_PRINT_EN \
- (UFS_QCOM_DBG_PRINT_REGS_EN | UFS_QCOM_DBG_PRINT_TEST_BUS_EN)
-
-enum {
- TSTBUS_UAWM,
- TSTBUS_UARM,
- TSTBUS_TXUC,
- TSTBUS_RXUC,
- TSTBUS_DFC,
- TSTBUS_TRLUT,
- TSTBUS_TMRLUT,
- TSTBUS_OCSC,
- TSTBUS_UTP_HCI,
- TSTBUS_COMBINED,
- TSTBUS_WRAPPER,
- TSTBUS_UNIPRO,
- TSTBUS_MAX,
-};
-
-static struct ufs_qcom_host *ufs_qcom_hosts[MAX_UFS_QCOM_HOSTS];
+static int 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 void ufs_qcom_get_default_testbus_cfg(struct ufs_qcom_host *host);
-static int ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(struct ufs_hba *hba,
- u32 clk_cycles);
-
-static void ufs_qcom_dump_regs(struct ufs_hba *hba, int offset, int len,
- char *prefix)
-{
- print_hex_dump(KERN_ERR, prefix,
- len > 4 ? DUMP_PREFIX_OFFSET : DUMP_PREFIX_NONE,
- 16, 4, (void __force *)hba->mmio_base + offset,
- len * 4, false);
-}
+static int ufs_qcom_update_sec_cfg(struct ufs_hba *hba, bool restore_sec_cfg);
static int ufs_qcom_get_connected_tx_lanes(struct ufs_hba *hba, u32 *tx_lanes)
{
@@ -122,33 +103,33 @@ static int ufs_qcom_enable_lane_clks(struct ufs_qcom_host *host)
if (host->is_lane_clks_enabled)
return 0;
- err = ufs_qcom_host_clk_enable(dev, "rx_lane0_sync_clk",
- host->rx_l0_sync_clk);
+ 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);
+ err = ufs_qcom_host_clk_enable(dev,
+ "rx_lane1_sync_clk", host->rx_l1_sync_clk);
if (err)
goto disable_rx_l0;
- err = ufs_qcom_host_clk_enable(dev, "rx_lane1_sync_clk",
- host->rx_l1_sync_clk);
+ err = ufs_qcom_host_clk_enable(dev,
+ "tx_lane0_sync_clk", host->tx_l0_sync_clk);
if (err)
- goto disable_tx_l0;
+ goto disable_rx_l1;
- err = ufs_qcom_host_clk_enable(dev, "tx_lane1_sync_clk",
- host->tx_l1_sync_clk);
+ err = ufs_qcom_host_clk_enable(dev,
+ "tx_lane1_sync_clk", host->tx_l1_sync_clk);
if (err)
- goto disable_rx_l1;
+ goto disable_tx_l0;
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_l1:
+ clk_disable_unprepare(host->rx_l1_sync_clk);
disable_rx_l0:
clk_disable_unprepare(host->rx_l0_sync_clk);
out:
@@ -166,25 +147,24 @@ static int ufs_qcom_init_lane_clks(struct ufs_qcom_host *host)
goto out;
err = ufs_qcom_host_clk_get(dev,
- "tx_lane0_sync_clk", &host->tx_l0_sync_clk);
+ "rx_lane1_sync_clk", &host->rx_l1_sync_clk);
if (err)
goto out;
- err = ufs_qcom_host_clk_get(dev, "rx_lane1_sync_clk",
- &host->rx_l1_sync_clk);
+ 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, "tx_lane1_sync_clk",
- &host->tx_l1_sync_clk);
-
+ 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 = ufshcd_get_variant(hba);
+ struct ufs_qcom_host *host = hba->priv;
struct phy *phy = host->generic_phy;
u32 tx_lanes;
int err = 0;
@@ -210,9 +190,7 @@ static int ufs_qcom_check_hibern8(struct ufs_hba *hba)
do {
err = ufshcd_dme_get(hba,
- UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE,
- UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)),
- &tx_fsm_val);
+ UIC_ARG_MIB(MPHY_TX_FSM_STATE), &tx_fsm_val);
if (err || tx_fsm_val == TX_FSM_HIBERN8)
break;
@@ -226,9 +204,7 @@ static int ufs_qcom_check_hibern8(struct ufs_hba *hba)
*/
if (time_after(jiffies, timeout))
err = ufshcd_dme_get(hba,
- UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE,
- UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)),
- &tx_fsm_val);
+ 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",
@@ -242,34 +218,25 @@ static int ufs_qcom_check_hibern8(struct ufs_hba *hba)
return err;
}
-static void ufs_qcom_select_unipro_mode(struct ufs_qcom_host *host)
-{
- ufshcd_rmwl(host->hba, QUNIPRO_SEL,
- ufs_qcom_cap_qunipro(host) ? QUNIPRO_SEL : 0,
- REG_UFS_CFG1);
- /* make sure above configuration is applied before we return */
- mb();
-}
-
static int ufs_qcom_power_up_sequence(struct ufs_hba *hba)
{
- struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ struct ufs_qcom_host *host = hba->priv;
struct phy *phy = host->generic_phy;
int ret = 0;
- bool is_rate_B = (UFS_QCOM_LIMIT_HS_RATE == PA_HS_MODE_B)
- ? true : false;
+ u8 major;
+ u16 minor, step;
/* 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);
- ret = ufs_qcom_phy_calibrate_phy(phy, is_rate_B);
-
+ 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);
if (ret) {
- dev_err(hba->dev,
- "%s: ufs_qcom_phy_calibrate_phy()failed, ret = %d\n",
- __func__, ret);
+ dev_err(hba->dev, "%s: ufs_qcom_phy_calibrate_phy() failed, ret = %d\n",
+ __func__, ret);
goto out;
}
@@ -290,38 +257,16 @@ static int ufs_qcom_power_up_sequence(struct ufs_hba *hba)
ret = ufs_qcom_phy_is_pcs_ready(phy);
if (ret)
- dev_err(hba->dev,
- "%s: is_physical_coding_sublayer_ready() failed, ret = %d\n",
+ dev_err(hba->dev, "%s: is_physical_coding_sublayer_ready() failed, ret = %d\n",
__func__, ret);
- ufs_qcom_select_unipro_mode(host);
-
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,
- enum ufs_notify_change_status status)
+static int ufs_qcom_hce_enable_notify(struct ufs_hba *hba, bool status)
{
- struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ struct ufs_qcom_host *host = hba->priv;
int err = 0;
switch (status) {
@@ -337,8 +282,6 @@ static int ufs_qcom_hce_enable_notify(struct ufs_hba *hba,
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);
@@ -349,13 +292,12 @@ static int ufs_qcom_hce_enable_notify(struct ufs_hba *hba,
}
/**
- * Returns zero for success and non-zero in case of a failure
+ * Returns non-zero for success (which rate of core_clk) and 0
+ * in case of a failure
*/
-static int ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear,
- u32 hs, u32 rate, bool update_link_startup_timer)
+static unsigned long
+ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear, u32 hs, u32 rate)
{
- int ret = 0;
- struct ufs_qcom_host *host = ufshcd_get_variant(hba);
struct ufs_clk_info *clki;
u32 core_clk_period_in_ns;
u32 tx_clk_cycles_per_us = 0;
@@ -372,25 +314,13 @@ static int ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear,
static u32 hs_fr_table_rA[][2] = {
{UFS_HS_G1, 0x1F},
{UFS_HS_G2, 0x3e},
- {UFS_HS_G3, 0x7D},
};
static u32 hs_fr_table_rB[][2] = {
{UFS_HS_G1, 0x24},
{UFS_HS_G2, 0x49},
- {UFS_HS_G3, 0x92},
};
- /*
- * The Qunipro controller does not use following registers:
- * SYS1CLK_1US_REG, TX_SYMBOL_CLK_1US_REG, CLK_NS_REG &
- * UFS_REG_PA_LINK_STARTUP_TIMER
- * But UTP controller uses SYS1CLK_1US_REG register for Interrupt
- * Aggregation logic.
- */
- if (ufs_qcom_cap_qunipro(host) && !ufshcd_is_intr_aggr_allowed(hba))
- goto out;
-
if (gear == 0) {
dev_err(hba->dev, "%s: invalid gear = %d\n", __func__, gear);
goto out_error;
@@ -406,17 +336,7 @@ static int ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear,
core_clk_rate = DEFAULT_CLK_RATE_HZ;
core_clk_cycles_per_us = core_clk_rate / USEC_PER_SEC;
- if (ufshcd_readl(hba, REG_UFS_SYS1CLK_1US) != core_clk_cycles_per_us) {
- ufshcd_writel(hba, core_clk_cycles_per_us, REG_UFS_SYS1CLK_1US);
- /*
- * make sure above write gets applied before we return from
- * this function.
- */
- mb();
- }
-
- if (ufs_qcom_cap_qunipro(host))
- goto out;
+ 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;
@@ -466,59 +386,35 @@ static int ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear,
goto out_error;
}
- if (ufshcd_readl(hba, REG_UFS_TX_SYMBOL_CLK_NS_US) !=
- (core_clk_period_in_ns | tx_clk_cycles_per_us)) {
- /* 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);
- /*
- * make sure above write gets applied before we return from
- * this function.
- */
- mb();
- }
-
- if (update_link_startup_timer) {
- ufshcd_writel(hba, ((core_clk_rate / MSEC_PER_SEC) * 100),
- REG_UFS_PA_LINK_STARTUP_TIMER);
- /*
- * make sure that this configuration is applied before
- * we return
- */
- mb();
- }
+ /* 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:
- ret = -EINVAL;
+ core_clk_rate = 0;
out:
- return ret;
+ return core_clk_rate;
}
-static int ufs_qcom_link_startup_notify(struct ufs_hba *hba,
- enum ufs_notify_change_status status)
+static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, bool status)
{
- int err = 0;
- struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ unsigned long core_clk_rate = 0;
+ u32 core_clk_cycles_per_100ms;
switch (status) {
case PRE_CHANGE:
- if (ufs_qcom_cfg_timers(hba, UFS_PWM_G1, SLOWAUTO_MODE,
- 0, true)) {
+ 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__);
- err = -EINVAL;
- goto out;
+ return -EINVAL;
}
-
- if (ufs_qcom_cap_qunipro(host))
- /*
- * set unipro core clock cycles to 150 & clear clock
- * divider
- */
- err = ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(hba,
- 150);
-
+ 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);
@@ -527,13 +423,12 @@ static int ufs_qcom_link_startup_notify(struct ufs_hba *hba,
break;
}
-out:
- return err;
+ return 0;
}
static int ufs_qcom_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
{
- struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ struct ufs_qcom_host *host = hba->priv;
struct phy *phy = host->generic_phy;
int ret = 0;
@@ -546,8 +441,6 @@ 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);
- /* Assert PHY soft reset */
- ufs_qcom_assert_reset(hba);
goto out;
}
@@ -556,7 +449,13 @@ static int ufs_qcom_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
* rail and low noise analog power rail for PLL can be switched off.
*/
if (!ufs_qcom_is_link_active(hba)) {
- ufs_qcom_disable_lane_clks(host);
+ if (ufs_qcom_phy_is_cfg_restore_quirk_enabled(phy) &&
+ ufs_qcom_is_link_hibern8(hba)) {
+ ret = ufs_qcom_phy_save_configuration(phy);
+ if (ret)
+ dev_err(hba->dev, "%s: failed ufs_qcom_phy_save_configuration %d\n",
+ __func__, ret);
+ }
phy_power_off(phy);
}
@@ -564,12 +463,28 @@ out:
return ret;
}
+static bool ufs_qcom_is_phy_config_restore_required(struct ufs_hba *hba)
+{
+ struct ufs_qcom_host *host = hba->priv;
+ struct phy *phy = host->generic_phy;
+
+ return ufs_qcom_phy_is_cfg_restore_quirk_enabled(phy)
+ && ufshcd_is_link_hibern8(hba)
+ && hba->is_sys_suspended;
+}
+
static int ufs_qcom_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
{
- struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ struct ufs_qcom_host *host = hba->priv;
struct phy *phy = host->generic_phy;
int err;
+ if (ufs_qcom_is_phy_config_restore_required(hba)) {
+ ufs_qcom_assert_reset(hba);
+ /* provide 1ms delay to let the reset pulse propagate */
+ usleep_range(1000, 1100);
+ }
+
err = phy_power_on(phy);
if (err) {
dev_err(hba->dev, "%s: failed enabling regs, err = %d\n",
@@ -577,12 +492,41 @@ static int ufs_qcom_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
goto out;
}
- err = ufs_qcom_enable_lane_clks(host);
- if (err)
- goto out;
+ if (ufs_qcom_is_phy_config_restore_required(hba)) {
+ ufs_qcom_phy_restore_swi_regs(phy);
- hba->is_sys_suspended = false;
+ /* 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);
+
+ err = ufs_qcom_phy_start_serdes(phy);
+ if (err) {
+ dev_err(hba->dev, "%s: ufs_qcom_phy_start_serdes() failed, err = %d\n",
+ __func__, err);
+ goto out;
+ }
+ err = ufs_qcom_phy_restore_configuration(phy);
+ if (err) {
+ dev_err(hba->dev, "%s: ufs_qcom_phy_restore_configuration() failed, err = %d\n",
+ __func__, err);
+ goto out;
+ }
+
+ err = ufs_qcom_phy_is_pcs_ready(phy);
+ if (err) {
+ dev_err(hba->dev, "%s: is_physical_coding_sublayer_ready() failed, err = %d\n",
+ __func__, err);
+ goto out;
+ }
+ }
+
+ hba->is_sys_suspended = false;
out:
return err;
}
@@ -602,162 +546,158 @@ struct ufs_qcom_dev_params {
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)
+/**
+ * as every power mode, according to the UFS spec, have a defined
+ * number that are not corresponed to their order or power
+ * consumption (i.e 5, 2, 4, 1 respectively from low to high),
+ * we need to map them into array, so we can scan it easily
+ * in order to find the minimum required power mode.
+ * also, we can use this routine to go the other way around,
+ * and from array index, the fetch the correspond power mode.
+ */
+static int map_unmap_pwr_mode(u32 mode, bool is_pwr_to_arr)
{
+ enum {SL_MD = 0, SLA_MD = 1, FS_MD = 2, FSA_MD = 3, UNDEF = 4};
+ int ret = -EINVAL;
+
+ if (is_pwr_to_arr) {
+ switch (mode) {
+ case SLOW_MODE:
+ ret = SL_MD;
+ break;
+ case SLOWAUTO_MODE:
+ ret = SLA_MD;
+ break;
+ case FAST_MODE:
+ ret = FS_MD;
+ break;
+ case FASTAUTO_MODE:
+ ret = FSA_MD;
+ break;
+ default:
+ ret = UNDEF;
+ break;
+ }
+ } else {
+ switch (mode) {
+ case SL_MD:
+ ret = SLOW_MODE;
+ break;
+ case SLA_MD:
+ ret = SLOWAUTO_MODE;
+ break;
+ case FS_MD:
+ ret = FAST_MODE;
+ break;
+ case FSA_MD:
+ ret = FASTAUTO_MODE;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+#define NUM_OF_SUPPORTED_MODES 5
+static int get_pwr_dev_param(struct ufs_qcom_dev_params *qcom_param,
+ struct ufs_pa_layer_attr *dev_max,
+ struct ufs_pa_layer_attr *dev_req)
+{
+ int arr[NUM_OF_SUPPORTED_MODES] = {0};
+ int i;
+ int min_power;
int min_qcom_gear;
int min_dev_gear;
- bool is_dev_sup_hs = false;
- bool is_qcom_max_hs = false;
+ bool is_max_dev_hs;
+ bool is_max_qcom_hs;
- if (dev_max->pwr_rx == FAST_MODE)
- is_dev_sup_hs = true;
+ /**
+ * mapping the max. supported power mode of the device
+ * and the max. pre-defined support power mode of the vendor
+ * in order to scan them easily
+ */
+ arr[map_unmap_pwr_mode(dev_max->pwr_rx, true)]++;
+ arr[map_unmap_pwr_mode(dev_max->pwr_tx, 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);
+ if (qcom_param->desired_working_mode == SLOW) {
+ arr[map_unmap_pwr_mode(qcom_param->rx_pwr_pwm, true)]++;
+ arr[map_unmap_pwr_mode(qcom_param->tx_pwr_pwm, true)]++;
} else {
- min_qcom_gear = min_t(u32, qcom_param->pwm_rx_gear,
- qcom_param->pwm_tx_gear);
+ arr[map_unmap_pwr_mode(qcom_param->rx_pwr_hs, true)]++;
+ arr[map_unmap_pwr_mode(qcom_param->tx_pwr_hs, true)]++;
}
- /*
- * 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;
+ for (i = 0; i < NUM_OF_SUPPORTED_MODES; ++i) {
+ if (arr[i] != 0)
+ break;
+ }
+
+ /* no supported power mode found */
+ if (i == NUM_OF_SUPPORTED_MODES) {
+ return -EINVAL;
} 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;
+ min_power = map_unmap_pwr_mode(i, false);
+ if (min_power >= 0)
+ dev_req->pwr_rx = dev_req->pwr_tx = min_power;
+ else
+ return -EINVAL;
}
- /*
+ /**
* 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
+ * 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);
+ dev_req->lane_tx = min_t(u32, dev_max->lane_tx, qcom_param->tx_lanes);
+ dev_req->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 */
+ if (dev_max->pwr_rx == SLOW_MODE ||
+ dev_max->pwr_rx == SLOWAUTO_MODE)
+ is_max_dev_hs = false;
+ else
+ is_max_dev_hs = true;
+
+ /* setting the device maximum gear */
min_dev_gear = min_t(u32, dev_max->gear_rx, dev_max->gear_tx);
- /*
+ /**
+ * setting the desired gear to be the minimum according to the desired
+ * power mode
+ */
+ if (qcom_param->desired_working_mode == SLOW) {
+ is_max_qcom_hs = false;
+ min_qcom_gear = min_t(u32, qcom_param->pwm_rx_gear,
+ qcom_param->pwm_tx_gear);
+ } else {
+ is_max_qcom_hs = true;
+ min_qcom_gear = min_t(u32, qcom_param->hs_rx_gear,
+ qcom_param->hs_tx_gear);
+ }
+
+ /**
* 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.
+ * 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
+ * what the gear, as he 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 =
+ if ((is_max_dev_hs && is_max_qcom_hs) ||
+ (!is_max_dev_hs && !is_max_qcom_hs)) {
+ dev_req->gear_rx = dev_req->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;
-}
-
-#ifdef CONFIG_MSM_BUS_SCALING
-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 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 if (!is_max_dev_hs) {
+ dev_req->gear_rx = dev_req->gear_tx = min_dev_gear;
} else {
- pwr = SLOW_MODE;
- snprintf(result, BUS_VECTOR_NAME_LEN, "%s_G%d_L%d",
- "PWM", gear, lanes);
+ dev_req->gear_rx = dev_req->gear_tx = min_qcom_gear;
}
-}
-static int ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote)
-{
- int err = 0;
-
- if (vote != host->bus_vote.curr_vote) {
- err = msm_bus_scale_client_update_request(
- host->bus_vote.client_handle, vote);
- if (err) {
- dev_err(host->hba->dev,
- "%s: msm_bus_scale_client_update_request() failed: bus_client_handle=0x%x, vote=%d, err=%d\n",
- __func__, host->bus_vote.client_handle,
- vote, err);
- goto out;
- }
+ dev_req->hs_rate = qcom_param->hs_rate;
- host->bus_vote.curr_vote = vote;
- }
-out:
- return err;
+ return 0;
}
static int ufs_qcom_update_bus_bw_vote(struct ufs_qcom_host *host)
@@ -766,7 +706,9 @@ static int ufs_qcom_update_bus_bw_vote(struct ufs_qcom_host *host)
int err = 0;
char mode[BUS_VECTOR_NAME_LEN];
- ufs_qcom_get_speed_mode(&host->dev_req_params, mode);
+ err = ufs_qcom_get_speed_mode(&host->dev_req_params, mode);
+ if (err)
+ goto out;
vote = ufs_qcom_get_bus_vote(host, mode);
if (vote >= 0)
@@ -774,6 +716,7 @@ static int ufs_qcom_update_bus_bw_vote(struct ufs_qcom_host *host)
else
err = vote;
+out:
if (err)
dev_err(host->hba->dev, "%s: failed %d\n", __func__, err);
else
@@ -781,137 +724,13 @@ static int ufs_qcom_update_bus_bw_vote(struct ufs_qcom_host *host)
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 = ufshcd_get_variant(hba);
-
- 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 = ufshcd_get_variant(hba);
- 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 msm_bus_scale_pdata *bus_pdata;
- struct device *dev = host->hba->dev;
- struct platform_device *pdev = to_platform_device(dev);
- struct device_node *np = dev->of_node;
-
- bus_pdata = msm_bus_cl_get_pdata(pdev);
- if (!bus_pdata) {
- dev_err(dev, "%s: failed to get bus vectors\n", __func__);
- err = -ENODATA;
- goto out;
- }
-
- err = of_property_count_strings(np, "qcom,bus-vector-names");
- if (err < 0 || err != bus_pdata->num_usecases) {
- dev_err(dev, "%s: qcom,bus-vector-names not specified correctly %d\n",
- __func__, err);
- goto out;
- }
-
- host->bus_vote.client_handle = msm_bus_scale_register_client(bus_pdata);
- if (!host->bus_vote.client_handle) {
- dev_err(dev, "%s: msm_bus_scale_register_client failed\n",
- __func__);
- err = -EFAULT;
- 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;
-}
-#else /* CONFIG_MSM_BUS_SCALING */
-static int ufs_qcom_update_bus_bw_vote(struct ufs_qcom_host *host)
-{
- return 0;
-}
-
-static int ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote)
-{
- return 0;
-}
-
-static int ufs_qcom_bus_register(struct ufs_qcom_host *host)
-{
- return 0;
-}
-#endif /* CONFIG_MSM_BUS_SCALING */
-
-static void ufs_qcom_dev_ref_clk_ctrl(struct ufs_qcom_host *host, bool enable)
-{
- if (host->dev_ref_clk_ctrl_mmio &&
- (enable ^ host->is_dev_ref_clk_enabled)) {
- u32 temp = readl_relaxed(host->dev_ref_clk_ctrl_mmio);
-
- if (enable)
- temp |= host->dev_ref_clk_en_mask;
- else
- temp &= ~host->dev_ref_clk_en_mask;
-
- /*
- * If we are here to disable this clock it might be immediately
- * after entering into hibern8 in which case we need to make
- * sure that device ref_clk is active at least 1us after the
- * hibern8 enter.
- */
- if (!enable)
- udelay(1);
-
- writel_relaxed(temp, host->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 at least 1us before the hibern8
- * exit command.
- */
- if (enable)
- udelay(1);
-
- host->is_dev_ref_clk_enabled = enable;
- }
-}
-
static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba,
- enum ufs_notify_change_status status,
- struct ufs_pa_layer_attr *dev_max_params,
- struct ufs_pa_layer_attr *dev_req_params)
+ 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 = ufshcd_get_variant(hba);
+ struct ufs_qcom_host *host = hba->priv;
struct phy *phy = host->generic_phy;
struct ufs_qcom_dev_params ufs_qcom_cap;
int ret = 0;
@@ -939,23 +758,8 @@ static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba,
ufs_qcom_cap.desired_working_mode =
UFS_QCOM_LIMIT_DESIRED_MODE;
- if (host->hw_ver.major == 0x1) {
- /*
- * HS-G3 operations may not reliably work on legacy QCOM
- * UFS host controller hardware even though capability
- * exchange during link startup phase may end up
- * negotiating maximum supported gear as G3.
- * Hence downgrade the maximum supported gear to HS-G2.
- */
- if (ufs_qcom_cap.hs_tx_gear > UFS_HS_G2)
- ufs_qcom_cap.hs_tx_gear = UFS_HS_G2;
- if (ufs_qcom_cap.hs_rx_gear > UFS_HS_G2)
- ufs_qcom_cap.hs_rx_gear = UFS_HS_G2;
- }
-
- ret = ufs_qcom_get_pwr_dev_param(&ufs_qcom_cap,
- dev_max_params,
- dev_req_params);
+ ret = get_pwr_dev_param(&ufs_qcom_cap, dev_max_params,
+ dev_req_params);
if (ret) {
pr_err("%s: failed to determine capabilities\n",
__func__);
@@ -964,9 +768,9 @@ static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba,
break;
case POST_CHANGE:
- if (ufs_qcom_cfg_timers(hba, dev_req_params->gear_rx,
+ if (!ufs_qcom_cfg_timers(hba, dev_req_params->gear_rx,
dev_req_params->pwr_rx,
- dev_req_params->hs_rate, false)) {
+ dev_req_params->hs_rate)) {
dev_err(hba->dev, "%s: ufs_qcom_cfg_timers() failed\n",
__func__);
/*
@@ -998,16 +802,6 @@ out:
return ret;
}
-static u32 ufs_qcom_get_ufs_hci_version(struct ufs_hba *hba)
-{
- struct ufs_qcom_host *host = ufshcd_get_variant(hba);
-
- if (host->hw_ver.major == 0x1)
- return UFSHCI_VERSION_11;
- else
- return UFSHCI_VERSION_20;
-}
-
/**
* ufs_qcom_advertise_quirks - advertise the known QCOM UFS controller quirks
* @hba: host controller instance
@@ -1019,54 +813,108 @@ static u32 ufs_qcom_get_ufs_hci_version(struct ufs_hba *hba)
*/
static void ufs_qcom_advertise_quirks(struct ufs_hba *hba)
{
- struct ufs_qcom_host *host = ufshcd_get_variant(hba);
-
- if (host->hw_ver.major == 0x01) {
- hba->quirks |= UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS
- | UFSHCD_QUIRK_BROKEN_PA_RXHSUNTERMCAP
- | UFSHCD_QUIRK_DME_PEER_ACCESS_AUTO_MODE;
+ u8 major;
+ u16 minor, step;
+
+ ufs_qcom_get_controller_revision(hba, &major, &minor, &step);
+
+ if ((major == 0x1) && (minor == 0x001) && (step == 0x0001))
+ hba->quirks |= (UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS
+ | UFSHCD_QUIRK_BROKEN_INTR_AGGR
+ | UFSHCD_QUIRK_BROKEN_PA_RXHSUNTERMCAP
+ | UFSHCD_QUIRK_BROKEN_LCC);
+ else if ((major == 0x1) && (minor == 0x002) && (step == 0x0000))
+ hba->quirks |= (UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS
+ | UFSHCD_QUIRK_BROKEN_PA_RXHSUNTERMCAP
+ | UFSHCD_QUIRK_BROKEN_LCC);
+}
- if (host->hw_ver.minor == 0x0001 && host->hw_ver.step == 0x0001)
- hba->quirks |= UFSHCD_QUIRK_BROKEN_INTR_AGGR;
+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";
- hba->quirks |= UFSHCD_QUIRK_BROKEN_LCC;
+ if (!speed_mode) {
+ err = -EINVAL;
+ goto out;
}
- if (host->hw_ver.major >= 0x2) {
- hba->quirks |= UFSHCD_QUIRK_BROKEN_UFS_HCI_VERSION;
+ 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) {
+ err = msm_bus_scale_client_update_request(
+ host->bus_vote.client_handle, vote);
+ if (err) {
+ dev_err(host->hba->dev,
+ "%s: msm_bus_scale_client_update_request() failed: bus_client_handle=0x%x, vote=%d, err=%d\n",
+ __func__, host->bus_vote.client_handle,
+ vote, err);
+ goto out;
+ }
- if (!ufs_qcom_cap_qunipro(host))
- /* Legacy UniPro mode still need following quirks */
- hba->quirks |= (UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS
- | UFSHCD_QUIRK_DME_PEER_ACCESS_AUTO_MODE
- | UFSHCD_QUIRK_BROKEN_PA_RXHSUNTERMCAP);
+ host->bus_vote.curr_vote = vote;
}
+out:
+ return err;
}
-static void ufs_qcom_set_caps(struct ufs_hba *hba)
+static int ufs_qcom_get_speed_mode(struct ufs_pa_layer_attr *p, char *result)
{
- struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ int err = 0;
+ int gear = max_t(u32, p->gear_rx, p->gear_tx);
+ int lanes = max_t(u32, p->lane_rx, p->lane_tx);
+ int pwr = max_t(u32, map_unmap_pwr_mode(p->pwr_rx, true),
+ map_unmap_pwr_mode(p->pwr_tx, true));
- hba->caps |= UFSHCD_CAP_CLK_GATING | UFSHCD_CAP_HIBERN8_WITH_CLK_GATING;
- hba->caps |= UFSHCD_CAP_CLK_SCALING;
- hba->caps |= UFSHCD_CAP_AUTO_BKOPS_SUSPEND;
+ /* 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 = 0;
- if (host->hw_ver.major >= 0x2) {
- host->caps = UFS_QCOM_CAP_QUNIPRO |
- UFS_QCOM_CAP_RETAIN_SEC_CFG_AFTER_PWR_COLLAPSE;
+ pwr = map_unmap_pwr_mode(pwr, false);
+ if (pwr < 0) {
+ err = pwr;
+ goto out;
}
+
+ if (pwr == FAST_MODE || pwr == FASTAUTO_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
+ snprintf(result, BUS_VECTOR_NAME_LEN, "%s_G%d_L%d",
+ "PWM", gear, lanes);
+out:
+ return err;
}
-/**
- * ufs_qcom_setup_clocks - enables/disable clocks
- * @hba: host controller instance
- * @on: If true, enable clocks else disable them.
- *
- * Returns 0 on success, non-zero on failure.
- */
+
+
static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on)
{
- struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ struct ufs_qcom_host *host = hba->priv;
int err;
int vote = 0;
@@ -1083,25 +931,12 @@ static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on)
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;
- }
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))
- /* disable device ref_clk */
- ufs_qcom_dev_ref_clk_ctrl(host, false);
-
vote = host->bus_vote.min_bw_vote;
}
@@ -1114,17 +949,85 @@ 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 msm_bus_scale_pdata *bus_pdata;
+ struct device *dev = host->hba->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct device_node *np = dev->of_node;
+
+ bus_pdata = msm_bus_cl_get_pdata(pdev);
+ if (!bus_pdata) {
+ dev_err(dev, "%s: failed to get bus vectors\n", __func__);
+ err = -ENODATA;
+ goto out;
+ }
+
+ err = of_property_count_strings(np, "qcom,bus-vector-names");
+ if (err < 0 || err != bus_pdata->num_usecases) {
+ dev_err(dev, "%s: qcom,bus-vector-names not specified correctly %d\n",
+ __func__, err);
+ goto out;
+ }
+
+ host->bus_vote.client_handle = msm_bus_scale_register_client(bus_pdata);
+ if (!host->bus_vote.client_handle) {
+ dev_err(dev, "%s: msm_bus_scale_register_client failed\n",
+ __func__);
+ err = -EFAULT;
+ 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];
-
-#ifndef MODULE
-static int __init get_android_boot_dev(char *str)
+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);
-#endif
/**
* ufs_qcom_init - bind phy with controller
@@ -1140,9 +1043,7 @@ static int ufs_qcom_init(struct ufs_hba *hba)
{
int err;
struct device *dev = hba->dev;
- struct platform_device *pdev = to_platform_device(dev);
struct ufs_qcom_host *host;
- struct resource *res;
if (strlen(android_boot_dev) && strcmp(android_boot_dev, dev_name(dev)))
return -ENODEV;
@@ -1154,58 +1055,24 @@ static int ufs_qcom_init(struct ufs_hba *hba)
goto out;
}
- /* Make a two way bind between the qcom host and the hba */
host->hba = hba;
- ufshcd_set_variant(hba, host);
-
- /*
- * voting/devoting device ref_clk source is time consuming hence
- * skip devoting it during aggressive clock gating. This clock
- * will still be gated off during runtime suspend.
- */
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);
+ dev_err(dev, "PHY get failed %d\n", err);
goto out;
}
+ hba->priv = (void *)host;
+
+ /* restore the secure configuration */
+ ufs_qcom_update_sec_cfg(hba, true);
+
err = ufs_qcom_bus_register(host);
if (err)
goto out_host_free;
- ufs_qcom_get_controller_revision(hba, &host->hw_ver.major,
- &host->hw_ver.minor, &host->hw_ver.step);
-
- /*
- * for newer controllers, device reference clock control bit has
- * moved inside UFS controller register address space itself.
- */
- if (host->hw_ver.major >= 0x02) {
- host->dev_ref_clk_ctrl_mmio = hba->mmio_base + REG_UFS_CFG1;
- host->dev_ref_clk_en_mask = BIT(26);
- } else {
- /* "dev_ref_clk_ctrl_mem" is optional resource */
- res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
- if (res) {
- host->dev_ref_clk_ctrl_mmio =
- devm_ioremap_resource(dev, res);
- if (IS_ERR(host->dev_ref_clk_ctrl_mmio)) {
- dev_warn(dev,
- "%s: could not map dev_ref_clk_ctrl_mmio, err %ld\n",
- __func__,
- PTR_ERR(host->dev_ref_clk_ctrl_mmio));
- host->dev_ref_clk_ctrl_mmio = NULL;
- }
- host->dev_ref_clk_en_mask = BIT(5);
- }
- }
-
- /* update phy revision information before calling phy_init() */
- ufs_qcom_phy_save_controller_version(host->generic_phy,
- host->hw_ver.major, host->hw_ver.minor, host->hw_ver.step);
-
phy_init(host->generic_phy);
err = phy_power_on(host->generic_phy);
if (err)
@@ -1215,295 +1082,122 @@ static int ufs_qcom_init(struct ufs_hba *hba)
if (err)
goto out_disable_phy;
- ufs_qcom_set_caps(hba);
ufs_qcom_advertise_quirks(hba);
+ hba->caps |= UFSHCD_CAP_CLK_GATING |
+ UFSHCD_CAP_HIBERN8_WITH_CLK_GATING;
+ 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;
-
- host->dbg_print_en |= UFS_QCOM_DEFAULT_DBG_PRINT_EN;
- ufs_qcom_get_default_testbus_cfg(host);
- err = ufs_qcom_testbus_config(host);
- if (err) {
- dev_warn(dev, "%s: failed to configure the testbus %d\n",
- __func__, err);
- err = 0;
- }
-
goto out;
out_disable_phy:
phy_power_off(host->generic_phy);
out_unregister_bus:
phy_exit(host->generic_phy);
+ msm_bus_scale_unregister_client(host->bus_vote.client_handle);
out_host_free:
devm_kfree(dev, host);
- ufshcd_set_variant(hba, NULL);
+ hba->priv = NULL;
out:
return err;
}
static void ufs_qcom_exit(struct ufs_hba *hba)
{
- struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ struct ufs_qcom_host *host = hba->priv;
+ msm_bus_scale_unregister_client(host->bus_vote.client_handle);
ufs_qcom_disable_lane_clks(host);
phy_power_off(host->generic_phy);
}
-static int ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(struct ufs_hba *hba,
- u32 clk_cycles)
-{
- int err;
- u32 core_clk_ctrl_reg;
-
- if (clk_cycles > DME_VS_CORE_CLK_CTRL_MAX_CORE_CLK_1US_CYCLES_MASK)
- return -EINVAL;
-
- err = ufshcd_dme_get(hba,
- UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL),
- &core_clk_ctrl_reg);
- if (err)
- goto out;
-
- core_clk_ctrl_reg &= ~DME_VS_CORE_CLK_CTRL_MAX_CORE_CLK_1US_CYCLES_MASK;
- core_clk_ctrl_reg |= clk_cycles;
-
- /* Clear CORE_CLK_DIV_EN */
- core_clk_ctrl_reg &= ~DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT;
-
- err = ufshcd_dme_set(hba,
- UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL),
- core_clk_ctrl_reg);
-out:
- return err;
-}
-
-static int ufs_qcom_clk_scale_up_pre_change(struct ufs_hba *hba)
-{
- /* nothing to do as of now */
- return 0;
-}
-
-static int ufs_qcom_clk_scale_up_post_change(struct ufs_hba *hba)
-{
- struct ufs_qcom_host *host = ufshcd_get_variant(hba);
-
- if (!ufs_qcom_cap_qunipro(host))
- return 0;
-
- /* set unipro core clock cycles to 150 and clear clock divider */
- return ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(hba, 150);
-}
-static int ufs_qcom_clk_scale_down_pre_change(struct ufs_hba *hba)
+void ufs_qcom_clk_scale_notify(struct ufs_hba *hba)
{
- struct ufs_qcom_host *host = ufshcd_get_variant(hba);
- int err;
- u32 core_clk_ctrl_reg;
-
- if (!ufs_qcom_cap_qunipro(host))
- return 0;
-
- err = ufshcd_dme_get(hba,
- UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL),
- &core_clk_ctrl_reg);
-
- /* make sure CORE_CLK_DIV_EN is cleared */
- if (!err &&
- (core_clk_ctrl_reg & DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT)) {
- core_clk_ctrl_reg &= ~DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT;
- err = ufshcd_dme_set(hba,
- UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL),
- core_clk_ctrl_reg);
- }
-
- return err;
-}
-
-static int ufs_qcom_clk_scale_down_post_change(struct ufs_hba *hba)
-{
- struct ufs_qcom_host *host = ufshcd_get_variant(hba);
-
- if (!ufs_qcom_cap_qunipro(host))
- return 0;
-
- /* set unipro core clock cycles to 75 and clear clock divider */
- return ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(hba, 75);
-}
-
-static int ufs_qcom_clk_scale_notify(struct ufs_hba *hba,
- bool scale_up, enum ufs_notify_change_status status)
-{
- struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ struct ufs_qcom_host *host = hba->priv;
struct ufs_pa_layer_attr *dev_req_params = &host->dev_req_params;
- int err = 0;
-
- if (status == PRE_CHANGE) {
- if (scale_up)
- err = ufs_qcom_clk_scale_up_pre_change(hba);
- else
- err = ufs_qcom_clk_scale_down_pre_change(hba);
- } else {
- if (scale_up)
- err = ufs_qcom_clk_scale_up_post_change(hba);
- else
- err = ufs_qcom_clk_scale_down_post_change(hba);
-
- if (err || !dev_req_params)
- goto out;
- ufs_qcom_cfg_timers(hba,
- dev_req_params->gear_rx,
- dev_req_params->pwr_rx,
- dev_req_params->hs_rate,
- false);
- ufs_qcom_update_bus_bw_vote(host);
- }
-
-out:
- return err;
-}
+ if (!dev_req_params)
+ return;
-static void ufs_qcom_get_default_testbus_cfg(struct ufs_qcom_host *host)
-{
- /* provide a legal default configuration */
- host->testbus.select_major = TSTBUS_UAWM;
- host->testbus.select_minor = 1;
+ ufs_qcom_cfg_timers(hba, dev_req_params->gear_rx,
+ dev_req_params->pwr_rx,
+ dev_req_params->hs_rate);
+ ufs_qcom_update_bus_bw_vote(host);
}
-static bool ufs_qcom_testbus_cfg_is_ok(struct ufs_qcom_host *host)
+/*
+ * This function should be called to restore the security configuration of UFS
+ * register space after coming out of UFS host core power collapse.
+ *
+ * @hba: host controller instance
+ * @restore_sec_cfg: Set "true" if secure configuration needs to be restored
+ * and set "false" when secure configuration is lost.
+ */
+static int ufs_qcom_update_sec_cfg(struct ufs_hba *hba, bool restore_sec_cfg)
{
- if (host->testbus.select_major >= TSTBUS_MAX) {
- dev_err(host->hba->dev,
- "%s: UFS_CFG1[TEST_BUS_SEL} may not equal 0x%05X\n",
- __func__, host->testbus.select_major);
- return false;
- }
-
- /*
- * Not performing check for each individual select_major
- * mappings of select_minor, since there is no harm in
- * configuring a non-existent select_minor
- */
- if (host->testbus.select_minor > 0x1F) {
- dev_err(host->hba->dev,
- "%s: 0x%05X is not a legal testbus option\n",
- __func__, host->testbus.select_minor);
- return false;
+ int ret = 0, scm_ret = 0;
+ struct ufs_qcom_host *host = hba->priv;
+
+ /* scm command buffer structrue */
+ struct msm_scm_cmd_buf {
+ unsigned int device_id;
+ unsigned int spare;
+ } cbuf = {0};
+ #define RESTORE_SEC_CFG_CMD 0x2
+ #define UFS_TZ_DEV_ID 19
+
+ if (!host || !hba->vreg_info.vdd_hba ||
+ !(host->sec_cfg_updated ^ restore_sec_cfg)) {
+ return 0;
+ } else if (!restore_sec_cfg) {
+ /*
+ * Clear the flag so next time when this function is called
+ * with restore_sec_cfg set to true, we can restore the secure
+ * configuration.
+ */
+ host->sec_cfg_updated = false;
+ goto out;
+ } else if (hba->clk_gating.state != CLKS_ON) {
+ /*
+ * Clocks should be ON to restore the host controller secure
+ * configuration.
+ */
+ goto out;
}
- return true;
-}
-
-int ufs_qcom_testbus_config(struct ufs_qcom_host *host)
-{
- int reg;
- int offset;
- u32 mask = TEST_BUS_SUB_SEL_MASK;
-
- if (!host)
- return -EINVAL;
-
- if (!ufs_qcom_testbus_cfg_is_ok(host))
- return -EPERM;
-
- switch (host->testbus.select_major) {
- case TSTBUS_UAWM:
- reg = UFS_TEST_BUS_CTRL_0;
- offset = 24;
- break;
- case TSTBUS_UARM:
- reg = UFS_TEST_BUS_CTRL_0;
- offset = 16;
- break;
- case TSTBUS_TXUC:
- reg = UFS_TEST_BUS_CTRL_0;
- offset = 8;
- break;
- case TSTBUS_RXUC:
- reg = UFS_TEST_BUS_CTRL_0;
- offset = 0;
- break;
- case TSTBUS_DFC:
- reg = UFS_TEST_BUS_CTRL_1;
- offset = 24;
- break;
- case TSTBUS_TRLUT:
- reg = UFS_TEST_BUS_CTRL_1;
- offset = 16;
- break;
- case TSTBUS_TMRLUT:
- reg = UFS_TEST_BUS_CTRL_1;
- offset = 8;
- break;
- case TSTBUS_OCSC:
- reg = UFS_TEST_BUS_CTRL_1;
- offset = 0;
- break;
- case TSTBUS_WRAPPER:
- reg = UFS_TEST_BUS_CTRL_2;
- offset = 16;
- break;
- case TSTBUS_COMBINED:
- reg = UFS_TEST_BUS_CTRL_2;
- offset = 8;
- break;
- case TSTBUS_UTP_HCI:
- reg = UFS_TEST_BUS_CTRL_2;
- offset = 0;
- break;
- case TSTBUS_UNIPRO:
- reg = UFS_UNIPRO_CFG;
- offset = 1;
- break;
/*
- * No need for a default case, since
- * ufs_qcom_testbus_cfg_is_ok() checks that the configuration
- * is legal
+ * If we are here, Host controller clocks are running, Host controller
+ * power collapse feature is supported and Host controller has just came
+ * out of power collapse.
*/
+ cbuf.device_id = UFS_TZ_DEV_ID;
+ ret = scm_restore_sec_cfg(cbuf.device_id, cbuf.spare, &scm_ret);
+ if (ret || scm_ret) {
+ dev_err(hba->dev, "%s: failed, ret %d scm_ret %d\n",
+ __func__, ret, scm_ret);
+ if (!ret)
+ ret = scm_ret;
+ } else {
+ host->sec_cfg_updated = true;
}
- mask <<= offset;
-
- pm_runtime_get_sync(host->hba->dev);
- ufshcd_hold(host->hba, false);
- ufshcd_rmwl(host->hba, TEST_BUS_SEL,
- (u32)host->testbus.select_major << 19,
- REG_UFS_CFG1);
- ufshcd_rmwl(host->hba, mask,
- (u32)host->testbus.select_minor << offset,
- reg);
- ufshcd_release(host->hba);
- pm_runtime_put_sync(host->hba->dev);
-
- return 0;
-}
-static void ufs_qcom_testbus_read(struct ufs_hba *hba)
-{
- ufs_qcom_dump_regs(hba, UFS_TEST_BUS, 1, "UFS_TEST_BUS ");
+out:
+ dev_dbg(hba->dev, "%s: ip: restore_sec_cfg %d, op: restore_sec_cfg %d, ret %d scm_ret %d\n",
+ __func__, restore_sec_cfg, host->sec_cfg_updated, ret, scm_ret);
+ return ret;
}
-static void ufs_qcom_dump_dbg_regs(struct ufs_hba *hba)
-{
- ufs_qcom_dump_regs(hba, REG_UFS_SYS1CLK_1US, 16,
- "HCI Vendor Specific Registers ");
-
- ufs_qcom_testbus_read(hba);
-}
/**
* struct ufs_hba_qcom_vops - UFS QCOM specific variant operations
*
* The variant operations configure the necessary controller and PHY
- * handshake during initialization.
+ * handshake during initializaiton.
*/
-static struct ufs_hba_variant_ops ufs_hba_qcom_vops = {
+const struct ufs_hba_variant_ops ufs_hba_qcom_vops = {
.name = "qcom",
.init = ufs_qcom_init,
.exit = ufs_qcom_exit,
- .get_ufs_hci_version = ufs_qcom_get_ufs_hci_version,
.clk_scale_notify = ufs_qcom_clk_scale_notify,
.setup_clocks = ufs_qcom_setup_clocks,
.hce_enable_notify = ufs_qcom_hce_enable_notify,
@@ -1511,6 +1205,7 @@ static struct ufs_hba_variant_ops ufs_hba_qcom_vops = {
.pwr_change_notify = ufs_qcom_pwr_change_notify,
.suspend = ufs_qcom_suspend,
.resume = ufs_qcom_resume,
+ .update_sec_cfg = ufs_qcom_update_sec_cfg,
.dbg_register_dump = ufs_qcom_dump_dbg_regs,
};
@@ -1571,6 +1266,4 @@ static struct platform_driver ufs_qcom_pltform = {
.of_match_table = of_match_ptr(ufs_qcom_of_match),
},
};
-module_platform_driver(ufs_qcom_pltform);
-
-MODULE_LICENSE("GPL v2");
+EXPORT_SYMBOL(ufs_hba_qcom_vops);
diff --git a/drivers/scsi/ufs/ufs-qcom.c~HEAD b/drivers/scsi/ufs/ufs-qcom.c~HEAD
new file mode 100644
index 000000000000..4f38d008bfb4
--- /dev/null
+++ b/drivers/scsi/ufs/ufs-qcom.c~HEAD
@@ -0,0 +1,1576 @@
+/*
+ * 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 "ufshcd-pltfrm.h"
+#include "unipro.h"
+#include "ufs-qcom.h"
+#include "ufshci.h"
+#define UFS_QCOM_DEFAULT_DBG_PRINT_EN \
+ (UFS_QCOM_DBG_PRINT_REGS_EN | UFS_QCOM_DBG_PRINT_TEST_BUS_EN)
+
+enum {
+ TSTBUS_UAWM,
+ TSTBUS_UARM,
+ TSTBUS_TXUC,
+ TSTBUS_RXUC,
+ TSTBUS_DFC,
+ TSTBUS_TRLUT,
+ TSTBUS_TMRLUT,
+ TSTBUS_OCSC,
+ TSTBUS_UTP_HCI,
+ TSTBUS_COMBINED,
+ TSTBUS_WRAPPER,
+ TSTBUS_UNIPRO,
+ TSTBUS_MAX,
+};
+
+static struct ufs_qcom_host *ufs_qcom_hosts[MAX_UFS_QCOM_HOSTS];
+
+static int ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote);
+static void ufs_qcom_get_default_testbus_cfg(struct ufs_qcom_host *host);
+static int ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(struct ufs_hba *hba,
+ u32 clk_cycles);
+
+static void ufs_qcom_dump_regs(struct ufs_hba *hba, int offset, int len,
+ char *prefix)
+{
+ print_hex_dump(KERN_ERR, prefix,
+ len > 4 ? DUMP_PREFIX_OFFSET : DUMP_PREFIX_NONE,
+ 16, 4, (void __force *)hba->mmio_base + offset,
+ len * 4, false);
+}
+
+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 = ufshcd_get_variant(hba);
+ 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_SEL(MPHY_TX_FSM_STATE,
+ UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)),
+ &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_SEL(MPHY_TX_FSM_STATE,
+ UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)),
+ &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 void ufs_qcom_select_unipro_mode(struct ufs_qcom_host *host)
+{
+ ufshcd_rmwl(host->hba, QUNIPRO_SEL,
+ ufs_qcom_cap_qunipro(host) ? QUNIPRO_SEL : 0,
+ REG_UFS_CFG1);
+ /* make sure above configuration is applied before we return */
+ mb();
+}
+
+static int ufs_qcom_power_up_sequence(struct ufs_hba *hba)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ struct phy *phy = host->generic_phy;
+ int ret = 0;
+ 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);
+
+ 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);
+
+ ufs_qcom_select_unipro_mode(host);
+
+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,
+ enum ufs_notify_change_status status)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ 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 zero for success and non-zero in case of a failure
+ */
+static int ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear,
+ u32 hs, u32 rate, bool update_link_startup_timer)
+{
+ int ret = 0;
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ 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},
+ {UFS_HS_G3, 0x7D},
+ };
+
+ static u32 hs_fr_table_rB[][2] = {
+ {UFS_HS_G1, 0x24},
+ {UFS_HS_G2, 0x49},
+ {UFS_HS_G3, 0x92},
+ };
+
+ /*
+ * The Qunipro controller does not use following registers:
+ * SYS1CLK_1US_REG, TX_SYMBOL_CLK_1US_REG, CLK_NS_REG &
+ * UFS_REG_PA_LINK_STARTUP_TIMER
+ * But UTP controller uses SYS1CLK_1US_REG register for Interrupt
+ * Aggregation logic.
+ */
+ if (ufs_qcom_cap_qunipro(host) && !ufshcd_is_intr_aggr_allowed(hba))
+ goto out;
+
+ 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;
+ if (ufshcd_readl(hba, REG_UFS_SYS1CLK_1US) != core_clk_cycles_per_us) {
+ ufshcd_writel(hba, core_clk_cycles_per_us, REG_UFS_SYS1CLK_1US);
+ /*
+ * make sure above write gets applied before we return from
+ * this function.
+ */
+ mb();
+ }
+
+ if (ufs_qcom_cap_qunipro(host))
+ goto out;
+
+ 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;
+ }
+
+ if (ufshcd_readl(hba, REG_UFS_TX_SYMBOL_CLK_NS_US) !=
+ (core_clk_period_in_ns | tx_clk_cycles_per_us)) {
+ /* 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);
+ /*
+ * make sure above write gets applied before we return from
+ * this function.
+ */
+ mb();
+ }
+
+ if (update_link_startup_timer) {
+ ufshcd_writel(hba, ((core_clk_rate / MSEC_PER_SEC) * 100),
+ REG_UFS_PA_LINK_STARTUP_TIMER);
+ /*
+ * make sure that this configuration is applied before
+ * we return
+ */
+ mb();
+ }
+ goto out;
+
+out_error:
+ ret = -EINVAL;
+out:
+ return ret;
+}
+
+static int ufs_qcom_link_startup_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ int err = 0;
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+
+ switch (status) {
+ case PRE_CHANGE:
+ if (ufs_qcom_cfg_timers(hba, UFS_PWM_G1, SLOWAUTO_MODE,
+ 0, true)) {
+ dev_err(hba->dev, "%s: ufs_qcom_cfg_timers() failed\n",
+ __func__);
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (ufs_qcom_cap_qunipro(host))
+ /*
+ * set unipro core clock cycles to 150 & clear clock
+ * divider
+ */
+ err = ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(hba,
+ 150);
+
+ break;
+ case POST_CHANGE:
+ ufs_qcom_link_startup_post_change(hba);
+ break;
+ default:
+ break;
+ }
+
+out:
+ return err;
+}
+
+static int ufs_qcom_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ 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)) {
+ ufs_qcom_disable_lane_clks(host);
+ 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 = ufshcd_get_variant(hba);
+ 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;
+ }
+
+ err = ufs_qcom_enable_lane_clks(host);
+ if (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;
+}
+
+#ifdef CONFIG_MSM_BUS_SCALING
+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 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_set_bus_vote(struct ufs_qcom_host *host, int vote)
+{
+ int err = 0;
+
+ if (vote != host->bus_vote.curr_vote) {
+ err = msm_bus_scale_client_update_request(
+ host->bus_vote.client_handle, vote);
+ if (err) {
+ dev_err(host->hba->dev,
+ "%s: msm_bus_scale_client_update_request() failed: bus_client_handle=0x%x, vote=%d, err=%d\n",
+ __func__, host->bus_vote.client_handle,
+ vote, err);
+ goto out;
+ }
+
+ host->bus_vote.curr_vote = vote;
+ }
+out:
+ return err;
+}
+
+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 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 = ufshcd_get_variant(hba);
+
+ 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 = ufshcd_get_variant(hba);
+ 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 msm_bus_scale_pdata *bus_pdata;
+ struct device *dev = host->hba->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct device_node *np = dev->of_node;
+
+ bus_pdata = msm_bus_cl_get_pdata(pdev);
+ if (!bus_pdata) {
+ dev_err(dev, "%s: failed to get bus vectors\n", __func__);
+ err = -ENODATA;
+ goto out;
+ }
+
+ err = of_property_count_strings(np, "qcom,bus-vector-names");
+ if (err < 0 || err != bus_pdata->num_usecases) {
+ dev_err(dev, "%s: qcom,bus-vector-names not specified correctly %d\n",
+ __func__, err);
+ goto out;
+ }
+
+ host->bus_vote.client_handle = msm_bus_scale_register_client(bus_pdata);
+ if (!host->bus_vote.client_handle) {
+ dev_err(dev, "%s: msm_bus_scale_register_client failed\n",
+ __func__);
+ err = -EFAULT;
+ 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;
+}
+#else /* CONFIG_MSM_BUS_SCALING */
+static int ufs_qcom_update_bus_bw_vote(struct ufs_qcom_host *host)
+{
+ return 0;
+}
+
+static int ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote)
+{
+ return 0;
+}
+
+static int ufs_qcom_bus_register(struct ufs_qcom_host *host)
+{
+ return 0;
+}
+#endif /* CONFIG_MSM_BUS_SCALING */
+
+static void ufs_qcom_dev_ref_clk_ctrl(struct ufs_qcom_host *host, bool enable)
+{
+ if (host->dev_ref_clk_ctrl_mmio &&
+ (enable ^ host->is_dev_ref_clk_enabled)) {
+ u32 temp = readl_relaxed(host->dev_ref_clk_ctrl_mmio);
+
+ if (enable)
+ temp |= host->dev_ref_clk_en_mask;
+ else
+ temp &= ~host->dev_ref_clk_en_mask;
+
+ /*
+ * If we are here to disable this clock it might be immediately
+ * after entering into hibern8 in which case we need to make
+ * sure that device ref_clk is active at least 1us after the
+ * hibern8 enter.
+ */
+ if (!enable)
+ udelay(1);
+
+ writel_relaxed(temp, host->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 at least 1us before the hibern8
+ * exit command.
+ */
+ if (enable)
+ udelay(1);
+
+ host->is_dev_ref_clk_enabled = enable;
+ }
+}
+
+static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *dev_max_params,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ u32 val;
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ 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;
+
+ if (host->hw_ver.major == 0x1) {
+ /*
+ * HS-G3 operations may not reliably work on legacy QCOM
+ * UFS host controller hardware even though capability
+ * exchange during link startup phase may end up
+ * negotiating maximum supported gear as G3.
+ * Hence downgrade the maximum supported gear to HS-G2.
+ */
+ if (ufs_qcom_cap.hs_tx_gear > UFS_HS_G2)
+ ufs_qcom_cap.hs_tx_gear = UFS_HS_G2;
+ if (ufs_qcom_cap.hs_rx_gear > UFS_HS_G2)
+ ufs_qcom_cap.hs_rx_gear = UFS_HS_G2;
+ }
+
+ 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, false)) {
+ 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;
+}
+
+static u32 ufs_qcom_get_ufs_hci_version(struct ufs_hba *hba)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+
+ if (host->hw_ver.major == 0x1)
+ return UFSHCI_VERSION_11;
+ else
+ return UFSHCI_VERSION_20;
+}
+
+/**
+ * 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)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+
+ if (host->hw_ver.major == 0x01) {
+ hba->quirks |= UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS
+ | UFSHCD_QUIRK_BROKEN_PA_RXHSUNTERMCAP
+ | UFSHCD_QUIRK_DME_PEER_ACCESS_AUTO_MODE;
+
+ if (host->hw_ver.minor == 0x0001 && host->hw_ver.step == 0x0001)
+ hba->quirks |= UFSHCD_QUIRK_BROKEN_INTR_AGGR;
+
+ hba->quirks |= UFSHCD_QUIRK_BROKEN_LCC;
+ }
+
+ if (host->hw_ver.major >= 0x2) {
+ hba->quirks |= UFSHCD_QUIRK_BROKEN_UFS_HCI_VERSION;
+
+ if (!ufs_qcom_cap_qunipro(host))
+ /* Legacy UniPro mode still need following quirks */
+ hba->quirks |= (UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS
+ | UFSHCD_QUIRK_DME_PEER_ACCESS_AUTO_MODE
+ | UFSHCD_QUIRK_BROKEN_PA_RXHSUNTERMCAP);
+ }
+}
+
+static void ufs_qcom_set_caps(struct ufs_hba *hba)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+
+ hba->caps |= UFSHCD_CAP_CLK_GATING | UFSHCD_CAP_HIBERN8_WITH_CLK_GATING;
+ hba->caps |= UFSHCD_CAP_CLK_SCALING;
+ hba->caps |= UFSHCD_CAP_AUTO_BKOPS_SUSPEND;
+
+ if (host->hw_ver.major >= 0x2) {
+ host->caps = UFS_QCOM_CAP_QUNIPRO |
+ UFS_QCOM_CAP_RETAIN_SEC_CFG_AFTER_PWR_COLLAPSE;
+ }
+}
+
+/**
+ * ufs_qcom_setup_clocks - enables/disable clocks
+ * @hba: host controller instance
+ * @on: If true, enable clocks else disable them.
+ *
+ * Returns 0 on success, non-zero on failure.
+ */
+static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ int err;
+ 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;
+ }
+ 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))
+ /* disable device ref_clk */
+ ufs_qcom_dev_ref_clk_ctrl(host, false);
+
+ 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;
+}
+
+#define ANDROID_BOOT_DEV_MAX 30
+static char android_boot_dev[ANDROID_BOOT_DEV_MAX];
+
+#ifndef MODULE
+static int __init get_android_boot_dev(char *str)
+{
+ strlcpy(android_boot_dev, str, ANDROID_BOOT_DEV_MAX);
+ return 1;
+}
+__setup("androidboot.bootdevice=", get_android_boot_dev);
+#endif
+
+/**
+ * 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 platform_device *pdev = to_platform_device(dev);
+ struct ufs_qcom_host *host;
+ struct resource *res;
+
+ 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;
+ }
+
+ /* Make a two way bind between the qcom host and the hba */
+ host->hba = hba;
+ ufshcd_set_variant(hba, host);
+
+ /*
+ * voting/devoting device ref_clk source is time consuming hence
+ * skip devoting it during aggressive clock gating. This clock
+ * will still be gated off during runtime suspend.
+ */
+ 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;
+
+ ufs_qcom_get_controller_revision(hba, &host->hw_ver.major,
+ &host->hw_ver.minor, &host->hw_ver.step);
+
+ /*
+ * for newer controllers, device reference clock control bit has
+ * moved inside UFS controller register address space itself.
+ */
+ if (host->hw_ver.major >= 0x02) {
+ host->dev_ref_clk_ctrl_mmio = hba->mmio_base + REG_UFS_CFG1;
+ host->dev_ref_clk_en_mask = BIT(26);
+ } else {
+ /* "dev_ref_clk_ctrl_mem" is optional resource */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (res) {
+ host->dev_ref_clk_ctrl_mmio =
+ devm_ioremap_resource(dev, res);
+ if (IS_ERR(host->dev_ref_clk_ctrl_mmio)) {
+ dev_warn(dev,
+ "%s: could not map dev_ref_clk_ctrl_mmio, err %ld\n",
+ __func__,
+ PTR_ERR(host->dev_ref_clk_ctrl_mmio));
+ host->dev_ref_clk_ctrl_mmio = NULL;
+ }
+ host->dev_ref_clk_en_mask = BIT(5);
+ }
+ }
+
+ /* update phy revision information before calling phy_init() */
+ ufs_qcom_phy_save_controller_version(host->generic_phy,
+ host->hw_ver.major, host->hw_ver.minor, host->hw_ver.step);
+
+ 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_set_caps(hba);
+ ufs_qcom_advertise_quirks(hba);
+
+ ufs_qcom_setup_clocks(hba, true);
+
+ if (hba->dev->id < MAX_UFS_QCOM_HOSTS)
+ ufs_qcom_hosts[hba->dev->id] = host;
+
+ host->dbg_print_en |= UFS_QCOM_DEFAULT_DBG_PRINT_EN;
+ ufs_qcom_get_default_testbus_cfg(host);
+ err = ufs_qcom_testbus_config(host);
+ if (err) {
+ dev_warn(dev, "%s: failed to configure the testbus %d\n",
+ __func__, err);
+ err = 0;
+ }
+
+ 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);
+ ufshcd_set_variant(hba, NULL);
+out:
+ return err;
+}
+
+static void ufs_qcom_exit(struct ufs_hba *hba)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+
+ ufs_qcom_disable_lane_clks(host);
+ phy_power_off(host->generic_phy);
+}
+
+static int ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(struct ufs_hba *hba,
+ u32 clk_cycles)
+{
+ int err;
+ u32 core_clk_ctrl_reg;
+
+ if (clk_cycles > DME_VS_CORE_CLK_CTRL_MAX_CORE_CLK_1US_CYCLES_MASK)
+ return -EINVAL;
+
+ err = ufshcd_dme_get(hba,
+ UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL),
+ &core_clk_ctrl_reg);
+ if (err)
+ goto out;
+
+ core_clk_ctrl_reg &= ~DME_VS_CORE_CLK_CTRL_MAX_CORE_CLK_1US_CYCLES_MASK;
+ core_clk_ctrl_reg |= clk_cycles;
+
+ /* Clear CORE_CLK_DIV_EN */
+ core_clk_ctrl_reg &= ~DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT;
+
+ err = ufshcd_dme_set(hba,
+ UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL),
+ core_clk_ctrl_reg);
+out:
+ return err;
+}
+
+static int ufs_qcom_clk_scale_up_pre_change(struct ufs_hba *hba)
+{
+ /* nothing to do as of now */
+ return 0;
+}
+
+static int ufs_qcom_clk_scale_up_post_change(struct ufs_hba *hba)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+
+ if (!ufs_qcom_cap_qunipro(host))
+ return 0;
+
+ /* set unipro core clock cycles to 150 and clear clock divider */
+ return ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(hba, 150);
+}
+
+static int ufs_qcom_clk_scale_down_pre_change(struct ufs_hba *hba)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ int err;
+ u32 core_clk_ctrl_reg;
+
+ if (!ufs_qcom_cap_qunipro(host))
+ return 0;
+
+ err = ufshcd_dme_get(hba,
+ UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL),
+ &core_clk_ctrl_reg);
+
+ /* make sure CORE_CLK_DIV_EN is cleared */
+ if (!err &&
+ (core_clk_ctrl_reg & DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT)) {
+ core_clk_ctrl_reg &= ~DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT;
+ err = ufshcd_dme_set(hba,
+ UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL),
+ core_clk_ctrl_reg);
+ }
+
+ return err;
+}
+
+static int ufs_qcom_clk_scale_down_post_change(struct ufs_hba *hba)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+
+ if (!ufs_qcom_cap_qunipro(host))
+ return 0;
+
+ /* set unipro core clock cycles to 75 and clear clock divider */
+ return ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(hba, 75);
+}
+
+static int ufs_qcom_clk_scale_notify(struct ufs_hba *hba,
+ bool scale_up, enum ufs_notify_change_status status)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ struct ufs_pa_layer_attr *dev_req_params = &host->dev_req_params;
+ int err = 0;
+
+ if (status == PRE_CHANGE) {
+ if (scale_up)
+ err = ufs_qcom_clk_scale_up_pre_change(hba);
+ else
+ err = ufs_qcom_clk_scale_down_pre_change(hba);
+ } else {
+ if (scale_up)
+ err = ufs_qcom_clk_scale_up_post_change(hba);
+ else
+ err = ufs_qcom_clk_scale_down_post_change(hba);
+
+ if (err || !dev_req_params)
+ goto out;
+
+ ufs_qcom_cfg_timers(hba,
+ dev_req_params->gear_rx,
+ dev_req_params->pwr_rx,
+ dev_req_params->hs_rate,
+ false);
+ ufs_qcom_update_bus_bw_vote(host);
+ }
+
+out:
+ return err;
+}
+
+static void ufs_qcom_get_default_testbus_cfg(struct ufs_qcom_host *host)
+{
+ /* provide a legal default configuration */
+ host->testbus.select_major = TSTBUS_UAWM;
+ host->testbus.select_minor = 1;
+}
+
+static bool ufs_qcom_testbus_cfg_is_ok(struct ufs_qcom_host *host)
+{
+ if (host->testbus.select_major >= TSTBUS_MAX) {
+ dev_err(host->hba->dev,
+ "%s: UFS_CFG1[TEST_BUS_SEL} may not equal 0x%05X\n",
+ __func__, host->testbus.select_major);
+ return false;
+ }
+
+ /*
+ * Not performing check for each individual select_major
+ * mappings of select_minor, since there is no harm in
+ * configuring a non-existent select_minor
+ */
+ if (host->testbus.select_minor > 0x1F) {
+ dev_err(host->hba->dev,
+ "%s: 0x%05X is not a legal testbus option\n",
+ __func__, host->testbus.select_minor);
+ return false;
+ }
+
+ return true;
+}
+
+int ufs_qcom_testbus_config(struct ufs_qcom_host *host)
+{
+ int reg;
+ int offset;
+ u32 mask = TEST_BUS_SUB_SEL_MASK;
+
+ if (!host)
+ return -EINVAL;
+
+ if (!ufs_qcom_testbus_cfg_is_ok(host))
+ return -EPERM;
+
+ switch (host->testbus.select_major) {
+ case TSTBUS_UAWM:
+ reg = UFS_TEST_BUS_CTRL_0;
+ offset = 24;
+ break;
+ case TSTBUS_UARM:
+ reg = UFS_TEST_BUS_CTRL_0;
+ offset = 16;
+ break;
+ case TSTBUS_TXUC:
+ reg = UFS_TEST_BUS_CTRL_0;
+ offset = 8;
+ break;
+ case TSTBUS_RXUC:
+ reg = UFS_TEST_BUS_CTRL_0;
+ offset = 0;
+ break;
+ case TSTBUS_DFC:
+ reg = UFS_TEST_BUS_CTRL_1;
+ offset = 24;
+ break;
+ case TSTBUS_TRLUT:
+ reg = UFS_TEST_BUS_CTRL_1;
+ offset = 16;
+ break;
+ case TSTBUS_TMRLUT:
+ reg = UFS_TEST_BUS_CTRL_1;
+ offset = 8;
+ break;
+ case TSTBUS_OCSC:
+ reg = UFS_TEST_BUS_CTRL_1;
+ offset = 0;
+ break;
+ case TSTBUS_WRAPPER:
+ reg = UFS_TEST_BUS_CTRL_2;
+ offset = 16;
+ break;
+ case TSTBUS_COMBINED:
+ reg = UFS_TEST_BUS_CTRL_2;
+ offset = 8;
+ break;
+ case TSTBUS_UTP_HCI:
+ reg = UFS_TEST_BUS_CTRL_2;
+ offset = 0;
+ break;
+ case TSTBUS_UNIPRO:
+ reg = UFS_UNIPRO_CFG;
+ offset = 1;
+ break;
+ /*
+ * No need for a default case, since
+ * ufs_qcom_testbus_cfg_is_ok() checks that the configuration
+ * is legal
+ */
+ }
+ mask <<= offset;
+
+ pm_runtime_get_sync(host->hba->dev);
+ ufshcd_hold(host->hba, false);
+ ufshcd_rmwl(host->hba, TEST_BUS_SEL,
+ (u32)host->testbus.select_major << 19,
+ REG_UFS_CFG1);
+ ufshcd_rmwl(host->hba, mask,
+ (u32)host->testbus.select_minor << offset,
+ reg);
+ ufshcd_release(host->hba);
+ pm_runtime_put_sync(host->hba->dev);
+
+ return 0;
+}
+
+static void ufs_qcom_testbus_read(struct ufs_hba *hba)
+{
+ ufs_qcom_dump_regs(hba, UFS_TEST_BUS, 1, "UFS_TEST_BUS ");
+}
+
+static void ufs_qcom_dump_dbg_regs(struct ufs_hba *hba)
+{
+ ufs_qcom_dump_regs(hba, REG_UFS_SYS1CLK_1US, 16,
+ "HCI Vendor Specific Registers ");
+
+ ufs_qcom_testbus_read(hba);
+}
+/**
+ * struct ufs_hba_qcom_vops - UFS QCOM specific variant operations
+ *
+ * The variant operations configure the necessary controller and PHY
+ * handshake during initialization.
+ */
+static struct ufs_hba_variant_ops ufs_hba_qcom_vops = {
+ .name = "qcom",
+ .init = ufs_qcom_init,
+ .exit = ufs_qcom_exit,
+ .get_ufs_hci_version = ufs_qcom_get_ufs_hci_version,
+ .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,
+ .dbg_register_dump = ufs_qcom_dump_dbg_regs,
+};
+
+/**
+ * ufs_qcom_probe - probe routine of the driver
+ * @pdev: pointer to Platform device handle
+ *
+ * Return zero for success and non-zero for failure
+ */
+static int ufs_qcom_probe(struct platform_device *pdev)
+{
+ int err;
+ struct device *dev = &pdev->dev;
+
+ /* Perform generic probe */
+ err = ufshcd_pltfrm_init(pdev, &ufs_hba_qcom_vops);
+ if (err)
+ dev_err(dev, "ufshcd_pltfrm_init() failed %d\n", err);
+
+ return err;
+}
+
+/**
+ * ufs_qcom_remove - set driver_data of the device to NULL
+ * @pdev: pointer to platform device handle
+ *
+ * Always return 0
+ */
+static int ufs_qcom_remove(struct platform_device *pdev)
+{
+ struct ufs_hba *hba = platform_get_drvdata(pdev);
+
+ pm_runtime_get_sync(&(pdev)->dev);
+ ufshcd_remove(hba);
+ return 0;
+}
+
+static const struct of_device_id ufs_qcom_of_match[] = {
+ { .compatible = "qcom,ufshc"},
+ {},
+};
+
+static const struct dev_pm_ops ufs_qcom_pm_ops = {
+ .suspend = ufshcd_pltfrm_suspend,
+ .resume = ufshcd_pltfrm_resume,
+ .runtime_suspend = ufshcd_pltfrm_runtime_suspend,
+ .runtime_resume = ufshcd_pltfrm_runtime_resume,
+ .runtime_idle = ufshcd_pltfrm_runtime_idle,
+};
+
+static struct platform_driver ufs_qcom_pltform = {
+ .probe = ufs_qcom_probe,
+ .remove = ufs_qcom_remove,
+ .shutdown = ufshcd_pltfrm_shutdown,
+ .driver = {
+ .name = "ufshcd-qcom",
+ .pm = &ufs_qcom_pm_ops,
+ .of_match_table = of_match_ptr(ufs_qcom_of_match),
+ },
+};
+module_platform_driver(ufs_qcom_pltform);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/scsi/ufs/ufs_quirks.c b/drivers/scsi/ufs/ufs_quirks.c
index 869c877f7f23..04197c61bdc2 100644
--- a/drivers/scsi/ufs/ufs_quirks.c
+++ b/drivers/scsi/ufs/ufs_quirks.c
@@ -1,4 +1,5 @@
-/* Copyright (c) 2014, The Linux Foundation. All rights reserved.
+/*
+ * Copyright (c) 2013-2014, 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
@@ -10,7 +11,7 @@
* GNU General Public License for more details.
*/
-#include "ufshcd.h"
+#include <linux/scsi/ufs/ufshcd.h>
#include "ufs_quirks.h"
diff --git a/drivers/scsi/ufs/ufs_test.c b/drivers/scsi/ufs/ufs_test.c
index 03c58a4ee2d6..c23e09e7c225 100644
--- a/drivers/scsi/ufs/ufs_test.c
+++ b/drivers/scsi/ufs/ufs_test.c
@@ -17,7 +17,9 @@
#include <linux/test-iosched.h>
#include <scsi/scsi_device.h>
#include <scsi/scsi_cmnd.h>
-#include <../sd.h>
+#include <scsi/scsi_host.h>
+#include <linux/scsi/ufs/ufshcd.h>
+#include <linux/scsi/ufs/ufs.h>
#define MODULE_NAME "ufs_test"
diff --git a/drivers/scsi/ufs/ufshcd-pci.c b/drivers/scsi/ufs/ufshcd-pci.c
index 52b546fb509b..650517925d51 100644
--- a/drivers/scsi/ufs/ufshcd-pci.c
+++ b/drivers/scsi/ufs/ufshcd-pci.c
@@ -33,7 +33,7 @@
* this program.
*/
-#include "ufshcd.h"
+#include <linux/scsi/ufs/ufshcd.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
diff --git a/drivers/scsi/ufs/ufshcd-pltfrm.c b/drivers/scsi/ufs/ufshcd-pltfrm.c
index dc3e5ce8766b..967a78470f73 100644
--- a/drivers/scsi/ufs/ufshcd-pltfrm.c
+++ b/drivers/scsi/ufs/ufshcd-pltfrm.c
@@ -39,6 +39,7 @@
#include "ufshcd.h"
#include "ufshcd-pltfrm.h"
+#include <linux/scsi/ufs/ufshcd.h>
static int ufshcd_parse_clock_info(struct ufs_hba *hba)
{
diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index 03d76aa7a619..cdc434ea1287 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -42,8 +42,10 @@
#include <linux/devfreq.h>
#include <linux/nls.h>
-#include "ufshcd.h"
-#include "unipro.h"
+#include <linux/scsi/ufs/ufshcd.h>
+#include <linux/scsi/ufs/unipro.h>
+#include "ufshci.h"
+#include "ufs_quirks.h"
#include "debugfs.h"
#define CREATE_TRACE_POINTS
@@ -3135,6 +3137,23 @@ out:
}
/**
+ * ufshcd_hba_stop - Send controller to reset state
+ * @hba: per adapter instance
+ * @can_sleep: perform sleep or just spin
+ */
+static inline void ufshcd_hba_stop(struct ufs_hba *hba, bool can_sleep)
+{
+ int err;
+
+ ufshcd_writel(hba, CONTROLLER_DISABLE, REG_CONTROLLER_ENABLE);
+ err = ufshcd_wait_for_register(hba, REG_CONTROLLER_ENABLE,
+ CONTROLLER_ENABLE, CONTROLLER_DISABLE,
+ 10, 1, can_sleep);
+ if (err)
+ dev_err(hba->dev, "%s: Controller disable failed\n", __func__);
+}
+
+/**
* ufshcd_hba_enable - initialize the controller
* @hba: per adapter instance
*
diff --git a/include/linux/phy/phy-qcom-ufs.h b/include/linux/phy/phy-qcom-ufs.h
index 9d18e9f948e9..689bd2491d2b 100644
--- a/include/linux/phy/phy-qcom-ufs.h
+++ b/include/linux/phy/phy-qcom-ufs.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
+ * Copyright (c) 2013-2014, 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
@@ -15,7 +15,9 @@
#ifndef PHY_QCOM_UFS_H_
#define PHY_QCOM_UFS_H_
-#include "phy.h"
+#include <linux/scsi/ufs/ufshcd.h>
+#include <linux/scsi/ufs/unipro.h>
+#include <linux/scsi/ufs/ufs-qcom.h>
/**
* ufs_qcom_phy_enable_ref_clk() - Enable the phy
diff --git a/drivers/scsi/ufs/ufs-msm.h b/include/linux/scsi/ufs/ufs-qcom.h
index c6d3689e8121..2bcb1edd53fa 100644
--- a/drivers/scsi/ufs/ufs-msm.h
+++ b/include/linux/scsi/ufs/ufs-qcom.h
@@ -139,15 +139,6 @@ struct ufs_qcom_host {
#define ufs_qcom_is_link_active(hba) ufshcd_is_link_active(hba)
#define ufs_qcom_is_link_hibern8(hba) ufshcd_is_link_hibern8(hba)
-enum {
- MASK_SERDES_START = 0x1,
- MASK_PCS_READY = 0x1,
-};
-
-enum {
- OFFSET_SERDES_START = 0x0,
-};
-
#define MAX_PROP_NAME 32
#define VDDA_PHY_MIN_UV 1000000
#define VDDA_PHY_MAX_UV 1000000
diff --git a/drivers/scsi/ufs/ufs.h b/include/linux/scsi/ufs/ufs.h
index d8b2a13f2053..d8b2a13f2053 100644
--- a/drivers/scsi/ufs/ufs.h
+++ b/include/linux/scsi/ufs/ufs.h
diff --git a/drivers/scsi/ufs/ufshcd.h b/include/linux/scsi/ufs/ufshcd.h
index 2e9aa73f2760..bb24bcb3bf30 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/include/linux/scsi/ufs/ufshcd.h
@@ -65,10 +65,7 @@
#include <scsi/scsi_eh.h>
#include <linux/fault-inject.h>
-
#include "ufs.h"
-#include "ufshci.h"
-#include "ufs_quirks.h"
#define UFSHCD "ufshcd"
#define UFSHCD_DRIVER_VERSION "0.2"
@@ -640,9 +637,9 @@ static inline bool ufshcd_is_intr_aggr_allowed(struct ufs_hba *hba)
}
#define ufshcd_writel(hba, val, reg) \
- writel((val), (hba)->mmio_base + (reg))
+ writel_relaxed((val), (hba)->mmio_base + (reg))
#define ufshcd_readl(hba, reg) \
- readl((hba)->mmio_base + (reg))
+ readl_relaxed((hba)->mmio_base + (reg))
/**
* ufshcd_rmwl - read modify write into a register
@@ -668,28 +665,6 @@ void ufshcd_remove(struct ufs_hba *);
int ufshcd_wait_for_register(struct ufs_hba *hba, u32 reg, u32 mask,
u32 val, unsigned long interval_us,
unsigned long timeout_ms, bool can_sleep);
-/**
- * ufshcd_hba_stop - Send controller to reset state
- * @hba: per adapter instance
- * @can_sleep: perform sleep or just spin
- */
-static inline void ufshcd_hba_stop(struct ufs_hba *hba, bool can_sleep)
-{
- int err;
-
- ufshcd_writel(hba, CONTROLLER_DISABLE, REG_CONTROLLER_ENABLE);
- err = ufshcd_wait_for_register(hba, REG_CONTROLLER_ENABLE,
- CONTROLLER_ENABLE, CONTROLLER_DISABLE,
- 10, 1, can_sleep);
- if (err)
- dev_err(hba->dev, "%s: Controller disable failed\n", __func__);
-}
-
-static inline void check_upiu_size(void)
-{
- BUILD_BUG_ON(ALIGNED_UPIU_SIZE <
- GENERAL_UPIU_REQUEST_SIZE + QUERY_DESC_MAX_SIZE);
-}
/**
* ufshcd_set_variant - set variant specific data to the hba
diff --git a/drivers/scsi/ufs/unipro.h b/include/linux/scsi/ufs/unipro.h
index 816a8a46efb8..6f9cd646930a 100644
--- a/drivers/scsi/ufs/unipro.h
+++ b/include/linux/scsi/ufs/unipro.h
@@ -1,6 +1,4 @@
/*
- * drivers/scsi/ufs/unipro.h
- *
* Copyright (C) 2013 Samsung Electronics Co., Ltd.
*
* This program is free software; you can redistribute it and/or modify