summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/scsi/ufs/debugfs.c128
-rw-r--r--drivers/scsi/ufs/ufshcd.c45
-rw-r--r--drivers/scsi/ufs/ufshcd.h3
3 files changed, 170 insertions, 6 deletions
diff --git a/drivers/scsi/ufs/debugfs.c b/drivers/scsi/ufs/debugfs.c
index d959e3c40b80..7ece8e048027 100644
--- a/drivers/scsi/ufs/debugfs.c
+++ b/drivers/scsi/ufs/debugfs.c
@@ -19,6 +19,7 @@
#include <linux/random.h>
#include "debugfs.h"
+#include "unipro.h"
enum field_width {
BYTE = 1,
@@ -599,6 +600,123 @@ static const struct file_operations ufsdbg_dump_device_desc = {
.read = seq_read,
};
+static int ufsdbg_power_mode_show(struct seq_file *file, void *data)
+{
+ struct ufs_hba *hba = (struct ufs_hba *)file->private;
+ char *names[] = {
+ "INVALID MODE",
+ "FAST MODE",
+ "SLOW MODE",
+ "INVALID MODE",
+ "FASTAUTO MODE",
+ "SLOWAUTO MODE",
+ "INVALID MODE",
+ };
+
+ /* Print current status */
+ seq_puts(file, "UFS current power mode [RX, TX]:");
+ seq_printf(file, "gear=[%d,%d], lane=[%d,%d], pwr=[%s,%s], rate = %c",
+ hba->pwr_info.gear_rx, hba->pwr_info.gear_tx,
+ hba->pwr_info.lane_rx, hba->pwr_info.lane_tx,
+ names[hba->pwr_info.pwr_rx],
+ names[hba->pwr_info.pwr_tx],
+ hba->pwr_info.hs_rate == PA_HS_MODE_B ? 'B' : 'A');
+ seq_puts(file, "\n\n");
+
+ /* Print usage */
+ seq_puts(file,
+ "To change power mode write 'GGLLMM' where:\n"
+ "G - selected gear\n"
+ "L - number of lanes\n"
+ "M - power mode:\n"
+ "\t1 = fast mode\n"
+ "\t2 = slow mode\n"
+ "\t4 = fast-auto mode\n"
+ "\t5 = slow-auto mode\n"
+ "first letter is for RX, second letter is for TX.\n\n");
+
+ return 0;
+}
+
+static bool ufsdbg_power_mode_validate(struct ufs_pa_layer_attr *pwr_mode)
+{
+ if (pwr_mode->gear_rx < UFS_PWM_G1 || pwr_mode->gear_rx > UFS_PWM_G7 ||
+ pwr_mode->gear_tx < UFS_PWM_G1 || pwr_mode->gear_tx > UFS_PWM_G7
+ || pwr_mode->lane_rx < 1 || pwr_mode->lane_rx > 2 ||
+ pwr_mode->lane_tx < 1 || pwr_mode->lane_tx > 2 ||
+ (pwr_mode->pwr_rx != FAST_MODE &&
+ pwr_mode->pwr_rx != SLOW_MODE &&
+ pwr_mode->pwr_rx != FASTAUTO_MODE &&
+ pwr_mode->pwr_rx != SLOWAUTO_MODE) ||
+ (pwr_mode->pwr_tx != FAST_MODE &&
+ pwr_mode->pwr_tx != SLOW_MODE &&
+ pwr_mode->pwr_tx != FASTAUTO_MODE &&
+ pwr_mode->pwr_tx != SLOWAUTO_MODE))
+ return false;
+
+ return true;
+}
+
+static ssize_t ufsdbg_power_mode_write(struct file *file,
+ const char __user *ubuf, size_t cnt,
+ loff_t *ppos)
+{
+ struct ufs_hba *hba = file->f_mapping->host->i_private;
+ struct ufs_pa_layer_attr pwr_mode;
+ char pwr_mode_str[BUFF_LINE_CAPACITY] = {0};
+ loff_t buff_pos = 0;
+ int ret;
+ int idx = 0;
+
+ ret = simple_write_to_buffer(pwr_mode_str, BUFF_LINE_CAPACITY,
+ &buff_pos, ubuf, cnt);
+
+ pwr_mode.gear_rx = pwr_mode_str[idx++] - '0';
+ pwr_mode.gear_tx = pwr_mode_str[idx++] - '0';
+ pwr_mode.lane_rx = pwr_mode_str[idx++] - '0';
+ pwr_mode.lane_tx = pwr_mode_str[idx++] - '0';
+ pwr_mode.pwr_rx = pwr_mode_str[idx++] - '0';
+ pwr_mode.pwr_tx = pwr_mode_str[idx++] - '0';
+ /*
+ * Switching between rates is not currently supported so use the
+ * current rate.
+ * TODO: add rate switching if and when it is supported in the future
+ */
+ pwr_mode.hs_rate = hba->pwr_info.hs_rate;
+
+ /* Validate user input */
+ if (!ufsdbg_power_mode_validate(&pwr_mode))
+ return -EINVAL;
+
+ pr_debug(
+ "%s: new power mode requested [RX,TX]: Gear=[%d,%d] Lanes=[%d,%d], Mode=[%d,%d]\n",
+ __func__, pwr_mode.gear_rx, pwr_mode.gear_tx, pwr_mode.lane_rx,
+ pwr_mode.lane_tx, pwr_mode.pwr_rx, pwr_mode.pwr_tx);
+
+ ret = ufshcd_config_pwr_mode(hba, &pwr_mode);
+ if (ret == -EBUSY)
+ dev_err(hba->dev,
+ "%s: ufshcd_config_pwr_mode failed: system is busy, try again\n",
+ __func__);
+ else if (ret)
+ dev_err(hba->dev,
+ "%s: ufshcd_config_pwr_mode failed, ret=%d\n",
+ __func__, ret);
+
+ return cnt;
+}
+
+static int ufsdbg_power_mode_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ufsdbg_power_mode_show, inode->i_private);
+}
+
+static const struct file_operations ufsdbg_power_mode_desc = {
+ .open = ufsdbg_power_mode_open,
+ .read = seq_read,
+ .write = ufsdbg_power_mode_write,
+};
+
void ufsdbg_add_debugfs(struct ufs_hba *hba)
{
if (!hba) {
@@ -672,6 +790,16 @@ void ufsdbg_add_debugfs(struct ufs_hba *hba)
goto err;
}
+ hba->debugfs_files.power_mode =
+ debugfs_create_file("power_mode", S_IRUSR | S_IWUSR,
+ hba->debugfs_files.debugfs_root, hba,
+ &ufsdbg_power_mode_desc);
+ if (!hba->debugfs_files.power_mode) {
+ dev_err(hba->dev,
+ "%s: NULL power_mode_desc file, exiting", __func__);
+ goto err;
+ }
+
ufsdbg_setup_fault_injection(hba);
return;
diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index ae9c76a55ae6..999e3f8b37f7 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -110,6 +110,9 @@
/* UIC command timeout, unit: ms */
#define UIC_CMD_TIMEOUT 500
+/* Retries waiting for doorbells to clear */
+#define POWER_MODE_RETRIES 10
+
/* NOP OUT retries waiting for NOP IN response */
#define NOP_OUT_RETRIES 10
/* Timeout after 30 msecs if NOP OUT hangs without response */
@@ -278,8 +281,6 @@ static int ufshcd_host_reset_and_restore(struct ufs_hba *hba);
static void ufshcd_resume_clkscaling(struct ufs_hba *hba);
static void ufshcd_suspend_clkscaling(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);
@@ -1259,6 +1260,7 @@ ufshcd_send_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd)
unsigned long flags;
ufshcd_hold(hba, false);
+ pm_runtime_get_sync(hba->dev);
mutex_lock(&hba->uic_cmd_mutex);
ufshcd_add_delay_before_dme_cmd(hba);
@@ -1269,7 +1271,7 @@ ufshcd_send_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd)
ret = ufshcd_wait_for_uic_cmd(hba, uic_cmd);
mutex_unlock(&hba->uic_cmd_mutex);
-
+ pm_runtime_put_sync(hba->dev);
ufshcd_release(hba);
return ret;
}
@@ -2709,14 +2711,45 @@ static int ufshcd_uic_pwr_ctrl(struct ufs_hba *hba, struct uic_command *cmd)
unsigned long flags;
u8 status;
int ret;
+ u32 tm_doorbell;
+ u32 tr_doorbell;
+ bool uic_ready;
+ int retries = POWER_MODE_RETRIES;
ufshcd_hold(hba, false);
+ pm_runtime_get_sync(hba->dev);
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);
+ /*
+ * Before changing the power mode there should be no outstanding
+ * tasks/transfer requests. Verify by checking the doorbell registers
+ * are clear.
+ */
+ do {
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ uic_ready = ufshcd_ready_for_uic_cmd(hba);
+ tm_doorbell = ufshcd_readl(hba, REG_UTP_TASK_REQ_DOOR_BELL);
+ tr_doorbell = ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL);
+ if (!tm_doorbell && !tr_doorbell && uic_ready)
+ break;
+
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ schedule();
+ retries--;
+ } while (retries && (tm_doorbell || tr_doorbell || !uic_ready));
+
+ if (!retries) {
+ dev_err(hba->dev,
+ "%s: too many retries waiting for doorbell to clear (tm=0x%x, tr=0x%x, uicrdy=%d)\n",
+ __func__, tm_doorbell, tr_doorbell, uic_ready);
+ ret = -EBUSY;
+ goto out;
+ }
+
hba->uic_async_done = &uic_async_done;
+
ret = __ufshcd_send_uic_cmd(hba, cmd);
spin_unlock_irqrestore(hba->host->host_lock, flags);
if (ret) {
@@ -2754,7 +2787,7 @@ out:
hba->uic_async_done = NULL;
spin_unlock_irqrestore(hba->host->host_lock, flags);
mutex_unlock(&hba->uic_cmd_mutex);
-
+ pm_runtime_put_sync(hba->dev);
ufshcd_release(hba);
return ret;
}
@@ -2981,7 +3014,7 @@ static int ufshcd_change_power_mode(struct ufs_hba *hba,
* @hba: per-adapter instance
* @desired_pwr_mode: desired power configuration
*/
-static int ufshcd_config_pwr_mode(struct ufs_hba *hba,
+int ufshcd_config_pwr_mode(struct ufs_hba *hba,
struct ufs_pa_layer_attr *desired_pwr_mode)
{
struct ufs_pa_layer_attr final_params = { 0 };
diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
index 324a65348979..d0961879b0f3 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -377,6 +377,7 @@ struct debugfs_files {
struct dentry *show_hba;
struct dentry *host_regs;
struct dentry *dump_dev_desc;
+ struct dentry *power_mode;
#ifdef CONFIG_UFS_FAULT_INJECTION
struct fault_attr fail_attr;
#endif
@@ -717,6 +718,8 @@ extern int ufshcd_dme_set_attr(struct ufs_hba *hba, u32 attr_sel,
u8 attr_set, u32 mib_val, u8 peer);
extern int ufshcd_dme_get_attr(struct ufs_hba *hba, u32 attr_sel,
u32 *mib_val, u8 peer);
+extern int ufshcd_config_pwr_mode(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *desired_pwr_mode);
/* UIC command interfaces for DME primitives */
#define DME_LOCAL 0