summaryrefslogtreecommitdiff
path: root/drivers/ufs/core
diff options
context:
space:
mode:
authorCan Guo <can.guo@oss.qualcomm.com>2026-03-25 08:21:43 -0700
committerMartin K. Petersen <martin.petersen@oracle.com>2026-03-27 17:20:29 -0400
commitd3eba21c71708746672587f1de2cc33e6a10d61a (patch)
treeaee4339998a516e6149840e25d4c224f6c741b37 /drivers/ufs/core
parent6de23f81a5e08be8fbf5e8d7e9febc72a5b5f27f (diff)
downloadlinux-next-d3eba21c71708746672587f1de2cc33e6a10d61a.tar.gz
linux-next-d3eba21c71708746672587f1de2cc33e6a10d61a.zip
scsi: ufs: core: Introduce a new ufshcd vops negotiate_pwr_mode()
Most vendor specific implemenations of vops pwr_change_notify(PRE_CHANGE) are fulfilling two things at once: - Vendor specific target power mode negotiation - Vendor specific power mode change preparation When TX Equalization is added into consideration, before power mode change to a target power mode, TX Equalization Training (EQTR) needs be done for that target power mode. In addition, UFSHCI spec requires to start TX EQTR from HS-G1 (the most reliable High Speed Gear). Adding TX EQTR before pwr_change_notify(PRE_CHANGE) is not applicable because we don't know the negotiated power mode yet. Adding TX EQTR post pwr_change_notify(PRE_CHANGE) is inappropriate because pwr_change_notify(PRE_CHANGE) has finished preparation for a power mode change to negotiated power mode, yet we are changing power mode to HS-G1 for TX EQTR. Add a new vops negotiate_pwr_mode() so that vendor specific power mode negotiation can be fulfilled in its vendor specific implementations. Later on, TX EQTR can be added post vops negotiate_pwr_mode() and before vops pwr_change_notify(PRE_CHANGE). Reviewed-by: Bean Huo <beanhuo@micron.com> Reviewed-by: Bart Van Assche <bvanassche@acm.org> Signed-off-by: Can Guo <can.guo@oss.qualcomm.com> Reviewed-by: Peter Wang <peter.wang@mediatek.com> Link: https://patch.msgid.link/20260325152154.1604082-2-can.guo@oss.qualcomm.com Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
Diffstat (limited to 'drivers/ufs/core')
-rw-r--r--drivers/ufs/core/ufshcd-priv.h14
-rw-r--r--drivers/ufs/core/ufshcd.c70
2 files changed, 68 insertions, 16 deletions
diff --git a/drivers/ufs/core/ufshcd-priv.h b/drivers/ufs/core/ufshcd-priv.h
index 37c32071e754..f1cec1cd01d2 100644
--- a/drivers/ufs/core/ufshcd-priv.h
+++ b/drivers/ufs/core/ufshcd-priv.h
@@ -167,14 +167,24 @@ static inline int ufshcd_vops_link_startup_notify(struct ufs_hba *hba,
return 0;
}
+static inline int ufshcd_vops_negotiate_pwr_mode(struct ufs_hba *hba,
+ const struct ufs_pa_layer_attr *dev_max_params,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ if (hba->vops && hba->vops->negotiate_pwr_mode)
+ return hba->vops->negotiate_pwr_mode(hba, dev_max_params,
+ dev_req_params);
+
+ return -ENOTSUPP;
+}
+
static inline int ufshcd_vops_pwr_change_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status,
- const struct ufs_pa_layer_attr *dev_max_params,
struct ufs_pa_layer_attr *dev_req_params)
{
if (hba->vops && hba->vops->pwr_change_notify)
return hba->vops->pwr_change_notify(hba, status,
- dev_max_params, dev_req_params);
+ dev_req_params);
return -ENOTSUPP;
}
diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
index 847b55789bb8..33bbdd940b06 100644
--- a/drivers/ufs/core/ufshcd.c
+++ b/drivers/ufs/core/ufshcd.c
@@ -336,8 +336,6 @@ static void ufshcd_suspend_clkscaling(struct ufs_hba *hba);
static int ufshcd_scale_clks(struct ufs_hba *hba, unsigned long freq,
bool scale_up);
static irqreturn_t ufshcd_intr(int irq, void *__hba);
-static int ufshcd_change_power_mode(struct ufs_hba *hba,
- struct ufs_pa_layer_attr *pwr_mode);
static int ufshcd_setup_hba_vreg(struct ufs_hba *hba, bool on);
static int ufshcd_setup_vreg(struct ufs_hba *hba, bool on);
static inline int ufshcd_config_vreg_hpm(struct ufs_hba *hba,
@@ -4663,8 +4661,26 @@ static int ufshcd_get_max_pwr_mode(struct ufs_hba *hba)
return 0;
}
-static int ufshcd_change_power_mode(struct ufs_hba *hba,
- struct ufs_pa_layer_attr *pwr_mode)
+/**
+ * ufshcd_dme_change_power_mode() - UniPro DME Power Mode change sequence
+ * @hba: per-adapter instance
+ * @pwr_mode: pointer to the target power mode (gear/lane) attributes
+ *
+ * This function handles the low-level DME (Device Management Entity)
+ * configuration required to transition the UFS link to a new power mode. It
+ * performs the following steps:
+ * 1. Checks if the requested mode matches the current state.
+ * 2. Sets M-PHY and UniPro attributes including Gear (PA_RXGEAR/TXGEAR),
+ * Lanes, Termination, and HS Series (PA_HSSERIES).
+ * 3. Configures default UniPro timeout values (DL_FC0, etc.) unless
+ * explicitly skipped via quirks.
+ * 4. Triggers the actual hardware mode change via ufshcd_uic_change_pwr_mode().
+ * 5. Updates the HBA's cached power information on success.
+ *
+ * Return: 0 on success, non-zero error code on failure.
+ */
+static int ufshcd_dme_change_power_mode(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode)
{
int ret;
@@ -4749,6 +4765,34 @@ static int ufshcd_change_power_mode(struct ufs_hba *hba,
}
/**
+ * ufshcd_change_power_mode() - Change UFS Link Power Mode
+ * @hba: per-adapter instance
+ * @pwr_mode: pointer to the target power mode (gear/lane) attributes
+ *
+ * This function handles the high-level sequence for changing the UFS link
+ * power mode. It triggers vendor-specific pre-change notification,
+ * executes the DME (Device Management Entity) power mode change sequence,
+ * and, upon success, triggers vendor-specific post-change notification.
+ *
+ * Return: 0 on success, non-zero error code on failure.
+ */
+int ufshcd_change_power_mode(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode)
+{
+ int ret;
+
+ ufshcd_vops_pwr_change_notify(hba, PRE_CHANGE, pwr_mode);
+
+ ret = ufshcd_dme_change_power_mode(hba, pwr_mode);
+
+ if (!ret)
+ ufshcd_vops_pwr_change_notify(hba, POST_CHANGE, pwr_mode);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufshcd_change_power_mode);
+
+/**
* ufshcd_config_pwr_mode - configure a new power mode
* @hba: per-adapter instance
* @desired_pwr_mode: desired power configuration
@@ -4761,19 +4805,17 @@ int ufshcd_config_pwr_mode(struct ufs_hba *hba,
struct ufs_pa_layer_attr final_params = { 0 };
int ret;
- ret = ufshcd_vops_pwr_change_notify(hba, PRE_CHANGE,
- desired_pwr_mode, &final_params);
+ ret = ufshcd_vops_negotiate_pwr_mode(hba, desired_pwr_mode,
+ &final_params);
+ if (ret) {
+ if (ret != -ENOTSUPP)
+ dev_err(hba->dev, "Failed to negotiate power mode: %d, use desired as is\n",
+ ret);
- if (ret)
memcpy(&final_params, desired_pwr_mode, sizeof(final_params));
+ }
- ret = ufshcd_change_power_mode(hba, &final_params);
-
- if (!ret)
- ufshcd_vops_pwr_change_notify(hba, POST_CHANGE, NULL,
- &final_params);
-
- return ret;
+ return ufshcd_change_power_mode(hba, &final_params);
}
EXPORT_SYMBOL_GPL(ufshcd_config_pwr_mode);