OSDN Git Service

scsi: ufs: Change power mode at run-time via debug-fs
authorGilad Broner <gbroner@codeaurora.org>
Tue, 4 Mar 2014 15:40:54 +0000 (17:40 +0200)
committerDavid Keitel <dkeitel@codeaurora.org>
Tue, 22 Mar 2016 17:56:57 +0000 (10:56 -0700)
Add 'power_mode' entry to UFS debug-fs to allow query of current
power mode status and changing the power mode by writing to the
entry a string in the format 'GGLLMM' where:
G - selected gear
L - number of lanes
M - power mode
    (1=fast mode, 2=slow mode, 4=fast-auto mode, 5=slow-auto mode)
First letter is for RX, second is for TX.

Change-Id: Ia48cb2719bb11e66bca923c5f4647a33cbd6c43e
Signed-off-by: Gilad Broner <gbroner@codeaurora.org>
[gbroner@codeaurora.org: fix merge conflicts]
Signed-off-by: Gilad Broner <gbroner@codeaurora.org>
[subhashj@codeaurora.org: resolved merge conflicts and compilation error]
Signed-off-by: Subhash Jadavani <subhashj@codeaurora.org>
[venkatg@codeaurora.org: resolved trivial merge conflicts]
Signed-off-by: Venkat Gopalakrishnan <venkatg@codeaurora.org>
drivers/scsi/ufs/debugfs.c
drivers/scsi/ufs/ufshcd.c
drivers/scsi/ufs/ufshcd.h

index d959e3c..7ece8e0 100644 (file)
@@ -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;
index ae9c76a..999e3f8 100644 (file)
 /* 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 };
index 324a653..d096187 100644 (file)
@@ -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