]> git.proxmox.com Git - mirror_ubuntu-focal-kernel.git/blobdiff - drivers/scsi/ufs/ufshcd.c
Merge tag 'for-4.2' of git://git.infradead.org/battery-2.6
[mirror_ubuntu-focal-kernel.git] / drivers / scsi / ufs / ufshcd.c
index 648a446758801ab6ff338d1dd9e5b5ab052df0da..b0ade73f8c6a66a6a5f4d32fcbc4ac266b43d7eb 100644 (file)
@@ -188,6 +188,8 @@ 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,
                struct ufs_pa_layer_attr *desired_pwr_mode);
+static int ufshcd_change_power_mode(struct ufs_hba *hba,
+                            struct ufs_pa_layer_attr *pwr_mode);
 
 static inline int ufshcd_enable_irq(struct ufs_hba *hba)
 {
@@ -269,6 +271,11 @@ static inline u32 ufshcd_get_intr_mask(struct ufs_hba *hba)
  */
 static inline u32 ufshcd_get_ufs_version(struct ufs_hba *hba)
 {
+       if (hba->quirks & UFSHCD_QUIRK_BROKEN_UFS_HCI_VERSION) {
+               if (hba->vops && hba->vops->get_ufs_hci_version)
+                       return hba->vops->get_ufs_hci_version(hba);
+       }
+
        return ufshcd_readl(hba, REG_UFS_VERSION);
 }
 
@@ -480,6 +487,15 @@ ufshcd_config_intr_aggr(struct ufs_hba *hba, u8 cnt, u8 tmout)
                      REG_UTP_TRANSFER_REQ_INT_AGG_CONTROL);
 }
 
+/**
+ * ufshcd_disable_intr_aggr - Disables interrupt aggregation.
+ * @hba: per adapter instance
+ */
+static inline void ufshcd_disable_intr_aggr(struct ufs_hba *hba)
+{
+       ufshcd_writel(hba, 0, REG_UTP_TRANSFER_REQ_INT_AGG_CONTROL);
+}
+
 /**
  * ufshcd_enable_run_stop_reg - Enable run-stop registers,
  *                     When run-stop registers are set to 1, it indicates the
@@ -1326,7 +1342,7 @@ static int ufshcd_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd)
        lrbp->sense_buffer = cmd->sense_buffer;
        lrbp->task_tag = tag;
        lrbp->lun = ufshcd_scsi_to_upiu_lun(cmd->device->lun);
-       lrbp->intr_cmd = false;
+       lrbp->intr_cmd = !ufshcd_is_intr_aggr_allowed(hba) ? true : false;
        lrbp->command_type = UTP_CMD_TYPE_SCSI;
 
        /* form UPIU before issuing the command */
@@ -2147,6 +2163,31 @@ int ufshcd_dme_get_attr(struct ufs_hba *hba, u32 attr_sel,
        };
        const char *get = action[!!peer];
        int ret;
+       struct ufs_pa_layer_attr orig_pwr_info;
+       struct ufs_pa_layer_attr temp_pwr_info;
+       bool pwr_mode_change = false;
+
+       if (peer && (hba->quirks & UFSHCD_QUIRK_DME_PEER_ACCESS_AUTO_MODE)) {
+               orig_pwr_info = hba->pwr_info;
+               temp_pwr_info = orig_pwr_info;
+
+               if (orig_pwr_info.pwr_tx == FAST_MODE ||
+                   orig_pwr_info.pwr_rx == FAST_MODE) {
+                       temp_pwr_info.pwr_tx = FASTAUTO_MODE;
+                       temp_pwr_info.pwr_rx = FASTAUTO_MODE;
+                       pwr_mode_change = true;
+               } else if (orig_pwr_info.pwr_tx == SLOW_MODE ||
+                   orig_pwr_info.pwr_rx == SLOW_MODE) {
+                       temp_pwr_info.pwr_tx = SLOWAUTO_MODE;
+                       temp_pwr_info.pwr_rx = SLOWAUTO_MODE;
+                       pwr_mode_change = true;
+               }
+               if (pwr_mode_change) {
+                       ret = ufshcd_change_power_mode(hba, &temp_pwr_info);
+                       if (ret)
+                               goto out;
+               }
+       }
 
        uic_cmd.command = peer ?
                UIC_CMD_DME_PEER_GET : UIC_CMD_DME_GET;
@@ -2161,6 +2202,10 @@ int ufshcd_dme_get_attr(struct ufs_hba *hba, u32 attr_sel,
 
        if (mib_val)
                *mib_val = uic_cmd.argument3;
+
+       if (peer && (hba->quirks & UFSHCD_QUIRK_DME_PEER_ACCESS_AUTO_MODE)
+           && pwr_mode_change)
+               ufshcd_change_power_mode(hba, &orig_pwr_info);
 out:
        return ret;
 }
@@ -2249,6 +2294,16 @@ static int ufshcd_uic_change_pwr_mode(struct ufs_hba *hba, u8 mode)
        struct uic_command uic_cmd = {0};
        int ret;
 
+       if (hba->quirks & UFSHCD_QUIRK_BROKEN_PA_RXHSUNTERMCAP) {
+               ret = ufshcd_dme_set(hba,
+                               UIC_ARG_MIB_SEL(PA_RXHSUNTERMCAP, 0), 1);
+               if (ret) {
+                       dev_err(hba->dev, "%s: failed to enable PA_RXHSUNTERMCAP ret %d\n",
+                                               __func__, ret);
+                       goto out;
+               }
+       }
+
        uic_cmd.command = UIC_CMD_DME_SET;
        uic_cmd.argument1 = UIC_ARG_MIB(PA_PWRMODE);
        uic_cmd.argument3 = mode;
@@ -2256,6 +2311,7 @@ static int ufshcd_uic_change_pwr_mode(struct ufs_hba *hba, u8 mode)
        ret = ufshcd_uic_pwr_ctrl(hba, &uic_cmd);
        ufshcd_release(hba);
 
+out:
        return ret;
 }
 
@@ -2522,7 +2578,10 @@ static int ufshcd_make_hba_operational(struct ufs_hba *hba)
        ufshcd_enable_intr(hba, UFSHCD_ENABLE_INTRS);
 
        /* Configure interrupt aggregation */
-       ufshcd_config_intr_aggr(hba, hba->nutrs - 1, INT_AGGR_DEF_TO);
+       if (ufshcd_is_intr_aggr_allowed(hba))
+               ufshcd_config_intr_aggr(hba, hba->nutrs - 1, INT_AGGR_DEF_TO);
+       else
+               ufshcd_disable_intr_aggr(hba);
 
        /* Configure UTRL and UTMRL base address registers */
        ufshcd_writel(hba, lower_32_bits(hba->utrdl_dma_addr),
@@ -2628,6 +2687,42 @@ static int ufshcd_hba_enable(struct ufs_hba *hba)
        return 0;
 }
 
+static int ufshcd_disable_tx_lcc(struct ufs_hba *hba, bool peer)
+{
+       int tx_lanes, i, err = 0;
+
+       if (!peer)
+               ufshcd_dme_get(hba, UIC_ARG_MIB(PA_CONNECTEDTXDATALANES),
+                              &tx_lanes);
+       else
+               ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_CONNECTEDTXDATALANES),
+                                   &tx_lanes);
+       for (i = 0; i < tx_lanes; i++) {
+               if (!peer)
+                       err = ufshcd_dme_set(hba,
+                               UIC_ARG_MIB_SEL(TX_LCC_ENABLE,
+                                       UIC_ARG_MPHY_TX_GEN_SEL_INDEX(i)),
+                                       0);
+               else
+                       err = ufshcd_dme_peer_set(hba,
+                               UIC_ARG_MIB_SEL(TX_LCC_ENABLE,
+                                       UIC_ARG_MPHY_TX_GEN_SEL_INDEX(i)),
+                                       0);
+               if (err) {
+                       dev_err(hba->dev, "%s: TX LCC Disable failed, peer = %d, lane = %d, err = %d",
+                               __func__, peer, i, err);
+                       break;
+               }
+       }
+
+       return err;
+}
+
+static inline int ufshcd_disable_device_tx_lcc(struct ufs_hba *hba)
+{
+       return ufshcd_disable_tx_lcc(hba, true);
+}
+
 /**
  * ufshcd_link_startup - Initialize unipro link startup
  * @hba: per adapter instance
@@ -2665,6 +2760,12 @@ static int ufshcd_link_startup(struct ufs_hba *hba)
                /* failed to get the link up... retire */
                goto out;
 
+       if (hba->quirks & UFSHCD_QUIRK_BROKEN_LCC) {
+               ret = ufshcd_disable_device_tx_lcc(hba);
+               if (ret)
+                       goto out;
+       }
+
        /* Include any host controller configuration via UIC commands */
        if (hba->vops && hba->vops->link_startup_notify) {
                ret = hba->vops->link_startup_notify(hba, POST_CHANGE);
@@ -3073,7 +3174,8 @@ static void ufshcd_transfer_req_compl(struct ufs_hba *hba)
         * false interrupt if device completes another request after resetting
         * aggregation and before reading the DB.
         */
-       ufshcd_reset_intr_aggr(hba);
+       if (ufshcd_is_intr_aggr_allowed(hba))
+               ufshcd_reset_intr_aggr(hba);
 
        tr_doorbell = ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL);
        completed_reqs = tr_doorbell ^ hba->outstanding_reqs;