scsi: ufs: Change power mode at run-time via debug-fs
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>
This commit is contained in:
parent
63078101ef
commit
0531f43f34
3 changed files with 170 additions and 6 deletions
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
#include <linux/random.h>
|
#include <linux/random.h>
|
||||||
#include "debugfs.h"
|
#include "debugfs.h"
|
||||||
|
#include "unipro.h"
|
||||||
|
|
||||||
enum field_width {
|
enum field_width {
|
||||||
BYTE = 1,
|
BYTE = 1,
|
||||||
|
@ -599,6 +600,123 @@ static const struct file_operations ufsdbg_dump_device_desc = {
|
||||||
.read = seq_read,
|
.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)
|
void ufsdbg_add_debugfs(struct ufs_hba *hba)
|
||||||
{
|
{
|
||||||
if (!hba) {
|
if (!hba) {
|
||||||
|
@ -672,6 +790,16 @@ void ufsdbg_add_debugfs(struct ufs_hba *hba)
|
||||||
goto err;
|
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);
|
ufsdbg_setup_fault_injection(hba);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -110,6 +110,9 @@
|
||||||
/* UIC command timeout, unit: ms */
|
/* UIC command timeout, unit: ms */
|
||||||
#define UIC_CMD_TIMEOUT 500
|
#define UIC_CMD_TIMEOUT 500
|
||||||
|
|
||||||
|
/* Retries waiting for doorbells to clear */
|
||||||
|
#define POWER_MODE_RETRIES 10
|
||||||
|
|
||||||
/* NOP OUT retries waiting for NOP IN response */
|
/* NOP OUT retries waiting for NOP IN response */
|
||||||
#define NOP_OUT_RETRIES 10
|
#define NOP_OUT_RETRIES 10
|
||||||
/* Timeout after 30 msecs if NOP OUT hangs without response */
|
/* 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_resume_clkscaling(struct ufs_hba *hba);
|
||||||
static void ufshcd_suspend_clkscaling(struct ufs_hba *hba);
|
static void ufshcd_suspend_clkscaling(struct ufs_hba *hba);
|
||||||
static irqreturn_t ufshcd_intr(int irq, void *__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,
|
static int ufshcd_change_power_mode(struct ufs_hba *hba,
|
||||||
struct ufs_pa_layer_attr *pwr_mode);
|
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;
|
unsigned long flags;
|
||||||
|
|
||||||
ufshcd_hold(hba, false);
|
ufshcd_hold(hba, false);
|
||||||
|
pm_runtime_get_sync(hba->dev);
|
||||||
mutex_lock(&hba->uic_cmd_mutex);
|
mutex_lock(&hba->uic_cmd_mutex);
|
||||||
ufshcd_add_delay_before_dme_cmd(hba);
|
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);
|
ret = ufshcd_wait_for_uic_cmd(hba, uic_cmd);
|
||||||
|
|
||||||
mutex_unlock(&hba->uic_cmd_mutex);
|
mutex_unlock(&hba->uic_cmd_mutex);
|
||||||
|
pm_runtime_put_sync(hba->dev);
|
||||||
ufshcd_release(hba);
|
ufshcd_release(hba);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -2709,14 +2711,45 @@ static int ufshcd_uic_pwr_ctrl(struct ufs_hba *hba, struct uic_command *cmd)
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
u8 status;
|
u8 status;
|
||||||
int ret;
|
int ret;
|
||||||
|
u32 tm_doorbell;
|
||||||
|
u32 tr_doorbell;
|
||||||
|
bool uic_ready;
|
||||||
|
int retries = POWER_MODE_RETRIES;
|
||||||
|
|
||||||
ufshcd_hold(hba, false);
|
ufshcd_hold(hba, false);
|
||||||
|
pm_runtime_get_sync(hba->dev);
|
||||||
mutex_lock(&hba->uic_cmd_mutex);
|
mutex_lock(&hba->uic_cmd_mutex);
|
||||||
init_completion(&uic_async_done);
|
init_completion(&uic_async_done);
|
||||||
ufshcd_add_delay_before_dme_cmd(hba);
|
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;
|
hba->uic_async_done = &uic_async_done;
|
||||||
|
|
||||||
ret = __ufshcd_send_uic_cmd(hba, cmd);
|
ret = __ufshcd_send_uic_cmd(hba, cmd);
|
||||||
spin_unlock_irqrestore(hba->host->host_lock, flags);
|
spin_unlock_irqrestore(hba->host->host_lock, flags);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
@ -2754,7 +2787,7 @@ out:
|
||||||
hba->uic_async_done = NULL;
|
hba->uic_async_done = NULL;
|
||||||
spin_unlock_irqrestore(hba->host->host_lock, flags);
|
spin_unlock_irqrestore(hba->host->host_lock, flags);
|
||||||
mutex_unlock(&hba->uic_cmd_mutex);
|
mutex_unlock(&hba->uic_cmd_mutex);
|
||||||
|
pm_runtime_put_sync(hba->dev);
|
||||||
ufshcd_release(hba);
|
ufshcd_release(hba);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -2981,7 +3014,7 @@ static int ufshcd_change_power_mode(struct ufs_hba *hba,
|
||||||
* @hba: per-adapter instance
|
* @hba: per-adapter instance
|
||||||
* @desired_pwr_mode: desired power configuration
|
* @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 *desired_pwr_mode)
|
||||||
{
|
{
|
||||||
struct ufs_pa_layer_attr final_params = { 0 };
|
struct ufs_pa_layer_attr final_params = { 0 };
|
||||||
|
|
|
@ -377,6 +377,7 @@ struct debugfs_files {
|
||||||
struct dentry *show_hba;
|
struct dentry *show_hba;
|
||||||
struct dentry *host_regs;
|
struct dentry *host_regs;
|
||||||
struct dentry *dump_dev_desc;
|
struct dentry *dump_dev_desc;
|
||||||
|
struct dentry *power_mode;
|
||||||
#ifdef CONFIG_UFS_FAULT_INJECTION
|
#ifdef CONFIG_UFS_FAULT_INJECTION
|
||||||
struct fault_attr fail_attr;
|
struct fault_attr fail_attr;
|
||||||
#endif
|
#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);
|
u8 attr_set, u32 mib_val, u8 peer);
|
||||||
extern int ufshcd_dme_get_attr(struct ufs_hba *hba, u32 attr_sel,
|
extern int ufshcd_dme_get_attr(struct ufs_hba *hba, u32 attr_sel,
|
||||||
u32 *mib_val, u8 peer);
|
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 */
|
/* UIC command interfaces for DME primitives */
|
||||||
#define DME_LOCAL 0
|
#define DME_LOCAL 0
|
||||||
|
|
Loading…
Add table
Reference in a new issue