ufs: add support to allow non standard behaviours (quirks)
authorYaniv Gardi <ygardi@codeaurora.org>
Tue, 31 Mar 2015 14:37:14 +0000 (17:37 +0300)
committerJames Bottomley <JBottomley@Odin.com>
Fri, 10 Apr 2015 15:53:56 +0000 (08:53 -0700)
Some implementation of UFS host controller HW might have some non-standard
behaviours (quirks) when compared to behaviour specified by UFSHCI
specification. This patch add support to allow specifying all such quirks
to standard UFS host controller driver so standard driver takes them into
account.

In this change a UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS is introduced,
where a minimum delay of 1ms is required before DME commands for
stability purposes.

Signed-off-by: Yaniv Gardi <ygardi@codeaurora.org>
Reviewed-by: Gilad Broner <gbroner@codeaurora.org>
Signed-off-by: James Bottomley <JBottomley@Odin.com>
drivers/scsi/ufs/ufs-qcom.c
drivers/scsi/ufs/ufs-qcom.h
drivers/scsi/ufs/ufshcd.c
drivers/scsi/ufs/ufshcd.h

index d7cb8435b697eb468258af6dfb7e1e036268c52e..6652a8171de60c41e07299559d3e993cb3d5163e 100644 (file)
@@ -694,13 +694,24 @@ out:
  */
 static void ufs_qcom_advertise_quirks(struct ufs_hba *hba)
 {
+       struct ufs_qcom_host *host = hba->priv;
 
+       if (host->hw_ver.major == 0x1)
+               hba->quirks |= UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS;
 
-       /*
-        * TBD
-        * here we should be advertising controller quirks according to
-        * controller version.
-        */
+       if (host->hw_ver.major >= 0x2) {
+               if (!ufs_qcom_cap_qunipro(host))
+                       /* Legacy UniPro mode still need following quirks */
+                       hba->quirks |= UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS;
+       }
+}
+
+static void ufs_qcom_set_caps(struct ufs_hba *hba)
+{
+       struct ufs_qcom_host *host = hba->priv;
+
+       if (host->hw_ver.major >= 0x2)
+               host->caps = UFS_QCOM_CAP_QUNIPRO;
 }
 
 static int ufs_qcom_get_bus_vote(struct ufs_qcom_host *host,
@@ -938,6 +949,7 @@ 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_CLK_SCALING;
index 9037396682289cf02bd296fba4aaf17a355b3244..db2c0a00e846282b37688e850a994e584ecb46d7 100644 (file)
@@ -158,6 +158,16 @@ struct ufs_hw_version {
        u8 major;
 };
 struct ufs_qcom_host {
+
+       /*
+        * Set this capability if host controller supports the QUniPro mode
+        * and if driver wants the Host controller to operate in QUniPro mode.
+        * Note: By default this capability will be kept enabled if host
+        * controller supports the QUniPro mode.
+        */
+       #define UFS_QCOM_CAP_QUNIPRO    UFS_BIT(0)
+       u32 caps;
+
        struct phy *generic_phy;
        struct ufs_hba *hba;
        struct ufs_qcom_bus_vote bus_vote;
@@ -175,4 +185,12 @@ 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)
 
+static inline bool ufs_qcom_cap_qunipro(struct ufs_qcom_host *host)
+{
+       if (host->caps & UFS_QCOM_CAP_QUNIPRO)
+               return true;
+       else
+               return false;
+}
+
 #endif /* UFS_QCOM_H_ */
index 5d60a868830dafe126d7d5bcdf73e959325919be..dfd72af28f060d08f9b9b6129c11344782b46c5f 100644 (file)
@@ -183,6 +183,7 @@ static int __ufshcd_setup_clocks(struct ufs_hba *hba, bool on,
 static int ufshcd_setup_clocks(struct ufs_hba *hba, bool on);
 static int ufshcd_uic_hibern8_exit(struct ufs_hba *hba);
 static int ufshcd_uic_hibern8_enter(struct ufs_hba *hba);
+static inline void ufshcd_add_delay_before_dme_cmd(struct ufs_hba *hba);
 static int ufshcd_host_reset_and_restore(struct ufs_hba *hba);
 static irqreturn_t ufshcd_intr(int irq, void *__hba);
 static int ufshcd_config_pwr_mode(struct ufs_hba *hba,
@@ -972,6 +973,8 @@ ufshcd_send_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd)
 
        ufshcd_hold(hba, false);
        mutex_lock(&hba->uic_cmd_mutex);
+       ufshcd_add_delay_before_dme_cmd(hba);
+
        spin_lock_irqsave(hba->host->host_lock, flags);
        ret = __ufshcd_send_uic_cmd(hba, uic_cmd);
        spin_unlock_irqrestore(hba->host->host_lock, flags);
@@ -2058,6 +2061,37 @@ static int ufshcd_dme_link_startup(struct ufs_hba *hba)
        return ret;
 }
 
+static inline void ufshcd_add_delay_before_dme_cmd(struct ufs_hba *hba)
+{
+       #define MIN_DELAY_BEFORE_DME_CMDS_US    1000
+       unsigned long min_sleep_time_us;
+
+       if (!(hba->quirks & UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS))
+               return;
+
+       /*
+        * last_dme_cmd_tstamp will be 0 only for 1st call to
+        * this function
+        */
+       if (unlikely(!ktime_to_us(hba->last_dme_cmd_tstamp))) {
+               min_sleep_time_us = MIN_DELAY_BEFORE_DME_CMDS_US;
+       } else {
+               unsigned long delta =
+                       (unsigned long) ktime_to_us(
+                               ktime_sub(ktime_get(),
+                               hba->last_dme_cmd_tstamp));
+
+               if (delta < MIN_DELAY_BEFORE_DME_CMDS_US)
+                       min_sleep_time_us =
+                               MIN_DELAY_BEFORE_DME_CMDS_US - delta;
+               else
+                       return; /* no more delay required */
+       }
+
+       /* allow sleep for extra 50us if needed */
+       usleep_range(min_sleep_time_us, min_sleep_time_us + 50);
+}
+
 /**
  * ufshcd_dme_set_attr - UIC command for DME_SET, DME_PEER_SET
  * @hba: per adapter instance
@@ -2157,6 +2191,7 @@ static int ufshcd_uic_pwr_ctrl(struct ufs_hba *hba, struct uic_command *cmd)
 
        mutex_lock(&hba->uic_cmd_mutex);
        init_completion(&uic_async_done);
+       ufshcd_add_delay_before_dme_cmd(hba);
 
        spin_lock_irqsave(hba->host->host_lock, flags);
        hba->uic_async_done = &uic_async_done;
index 4a574aa458557a14ecc15b0d96d69dc8fa1fc9d8..b47ff07698e80aa9119633568324e009da5217be 100644 (file)
@@ -366,6 +366,7 @@ struct ufs_init_prefetch {
  * @saved_err: sticky error mask
  * @saved_uic_err: sticky UIC error mask
  * @dev_cmd: ufs device management command information
+ * @last_dme_cmd_tstamp: time stamp of the last completed DME command
  * @auto_bkops_enabled: to track whether bkops is enabled in device
  * @vreg_info: UFS device voltage regulator information
  * @clk_list_head: UFS host controller clocks list node head
@@ -416,6 +417,13 @@ struct ufs_hba {
        unsigned int irq;
        bool is_irq_enabled;
 
+       /*
+        * delay before each dme command is required as the unipro
+        * layer has shown instabilities
+        */
+       #define UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS              UFS_BIT(0)
+
+       unsigned int quirks;    /* Deviations from standard UFSHCI spec. */
 
        wait_queue_head_t tm_wq;
        wait_queue_head_t tm_tag_wq;
@@ -446,6 +454,7 @@ struct ufs_hba {
 
        /* Device management request data */
        struct ufs_dev_cmd dev_cmd;
+       ktime_t last_dme_cmd_tstamp;
 
        /* Keeps information of the UFS device connected to this host */
        struct ufs_dev_info dev_info;