summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSujit Reddy Thumma <sthumma@codeaurora.org>2013-11-20 20:11:40 +0530
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-22 10:56:10 -0700
commitff109f0fc32057f3359599fb86f6aed3dca5b5c3 (patch)
treef4ac582f3976e63e9c16bb1c30bc8b2fc6122b35
parent743464f0edd51793f32bcff62f448d1324874af6 (diff)
scsi: ufs-msm: Fix turning off tx/rx lane synchronization clocks
The UFS PHY outputs tx/rx lane synchronization clocks which are input to host controller via branch gating logic in GCC. These clocks are enabled only when the PHY is powered on and Tx/Rx bursts are active. Gating these clocks at runtime might lead to insufficient clocks for the controller to close the tx/rx bursts. This further lead to data transfer getting stuck after clocks are ungated. Fix this by controlling the explicit enable/disable of these clocks only when PHY state is changed. There will be no power impact of leaving the clocks enabled at runtime because PHY dynamically manages the gating and the clocks are really enabled only when tx/rx bursts are active. CRs-Fixed: 576719 Change-Id: Ib74bff641063eab36c7fd3e5fb7829c46a11aee0 Signed-off-by: Sujit Reddy Thumma <sthumma@codeaurora.org> [gbroner@codeaurora.org: fix merge conflicts - apq8084.dtsi file location has been previously changed and is already up to date] Signed-off-by: Gilad Broner <gbroner@codeaurora.org>
-rw-r--r--drivers/scsi/ufs/ufs-msm.c144
-rw-r--r--drivers/scsi/ufs/ufs-msm.h5
2 files changed, 143 insertions, 6 deletions
diff --git a/drivers/scsi/ufs/ufs-msm.c b/drivers/scsi/ufs/ufs-msm.c
index a30c6e7926b3..aa8f49e5709b 100644
--- a/drivers/scsi/ufs/ufs-msm.c
+++ b/drivers/scsi/ufs/ufs-msm.c
@@ -851,6 +851,116 @@ static void msm_ufs_phy_calibrate(struct msm_ufs_phy *phy)
mb();
}
+static int msm_ufs_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 msm_ufs_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 msm_ufs_disable_lane_clks(struct msm_ufs_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 msm_ufs_enable_lane_clks(struct msm_ufs_host *host)
+{
+ int err = 0;
+ struct device *dev = host->hba->dev;
+
+ if (host->is_lane_clks_enabled)
+ return 0;
+
+ err = msm_ufs_host_clk_enable(dev,
+ "rx_lane0_sync_clk", host->rx_l0_sync_clk);
+ if (err)
+ goto out;
+
+ err = msm_ufs_host_clk_enable(dev,
+ "rx_lane1_sync_clk", host->rx_l1_sync_clk);
+ if (err)
+ goto disable_rx_l0;
+
+ err = msm_ufs_host_clk_enable(dev,
+ "tx_lane0_sync_clk", host->tx_l0_sync_clk);
+ if (err)
+ goto disable_rx_l1;
+
+ err = msm_ufs_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 msm_ufs_init_lane_clks(struct msm_ufs_host *host)
+{
+ int err = 0;
+ struct device *dev = host->hba->dev;
+
+ err = msm_ufs_host_clk_get(dev,
+ "rx_lane0_sync_clk", &host->rx_l0_sync_clk);
+ if (err)
+ goto out;
+
+ err = msm_ufs_host_clk_get(dev,
+ "rx_lane1_sync_clk", &host->rx_l1_sync_clk);
+ if (err)
+ goto out;
+
+ err = msm_ufs_host_clk_get(dev,
+ "tx_lane0_sync_clk", &host->tx_l0_sync_clk);
+ if (err)
+ goto out;
+
+ err = msm_ufs_host_clk_get(dev,
+ "tx_lane1_sync_clk", &host->tx_l1_sync_clk);
+out:
+ return err;
+}
+
static int msm_ufs_enable_tx_lanes(struct ufs_hba *hba)
{
int err;
@@ -1013,9 +1123,16 @@ static int msm_ufs_hce_enable_notify(struct ufs_hba *hba, bool status)
/* poll for PCS_READY for max. 1sec */
err = readl_poll_timeout(phy->mmio + UFS_PHY_PCS_READY_STATUS,
val, (val & MASK_PCS_READY), 10, 1000000);
- if (err)
+ if (err) {
dev_err(phy->dev, "%s: phy init failed, %d\n",
__func__, err);
+ break;
+ }
+ /*
+ * The PHY PLL output is the source of tx/rx lane symbol clocks.
+ * Hence, enable the lane clocks only after PHY is initialized.
+ */
+ err = msm_ufs_enable_lane_clks(host);
break;
case POST_CHANGE:
/* check if UFS PHY moved from DISABLED to HIBERN8 */
@@ -1060,6 +1177,12 @@ static int msm_ufs_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
return 0;
if (ufshcd_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.
+ */
+ msm_ufs_disable_lane_clks(host);
msm_ufs_phy_power_off(phy);
goto out;
}
@@ -1680,7 +1803,11 @@ static int msm_ufs_init(struct ufs_hba *hba)
err = msm_ufs_phy_power_on(phy);
if (err)
- goto out_host_free;
+ goto out_unregister_bus;
+
+ err = msm_ufs_init_lane_clks(host);
+ if (err)
+ goto out_disable_phy;
msm_ufs_advertise_quirks(hba);
if (hba->quirks & UFSHCD_QUIRK_BROKEN_SUSPEND) {
@@ -1709,11 +1836,15 @@ static int msm_ufs_init(struct ufs_hba *hba)
hba->spm_lvl = UFS_PM_LVL_3;
}
+ goto out;
+
+out_disable_phy:
+ msm_ufs_phy_power_off(phy);
+out_unregister_bus:
+ msm_bus_scale_unregister_client(host->bus_vote.client_handle);
out_host_free:
- if (err) {
- devm_kfree(dev, host);
- hba->priv = NULL;
- }
+ devm_kfree(dev, host);
+ hba->priv = NULL;
out:
return err;
}
@@ -1724,6 +1855,7 @@ static void msm_ufs_exit(struct ufs_hba *hba)
struct msm_ufs_phy *phy = host->phy;
msm_bus_scale_unregister_client(host->bus_vote.client_handle);
+ msm_ufs_disable_lane_clks(host);
msm_ufs_phy_power_off(phy);
}
diff --git a/drivers/scsi/ufs/ufs-msm.h b/drivers/scsi/ufs/ufs-msm.h
index 8b8fc9dd571d..37ec02971d1f 100644
--- a/drivers/scsi/ufs/ufs-msm.h
+++ b/drivers/scsi/ufs/ufs-msm.h
@@ -93,6 +93,11 @@ struct msm_ufs_host {
struct ufs_hba *hba;
struct msm_ufs_bus_vote bus_vote;
struct ufs_pa_layer_attr dev_req_params;
+ struct clk *rx_l0_sync_clk;
+ struct clk *tx_l0_sync_clk;
+ struct clk *rx_l1_sync_clk;
+ struct clk *tx_l1_sync_clk;
+ bool is_lane_clks_enabled;
};
static unsigned long