Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e21cdd59 authored by Gilad Broner's avatar Gilad Broner
Browse files

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: default avatarGilad Broner <gbroner@codeaurora.org>
parent c1d22233
Loading
Loading
Loading
Loading
+128 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@

#include <linux/random.h>
#include "debugfs.h"
#include "unipro.h"

enum field_width {
	BYTE	= 1,
@@ -484,6 +485,123 @@ static const struct file_operations ufsdbg_dump_device_desc = {
	.read		= seq_read,
};

static ssize_t 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) {
@@ -547,6 +665,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;
+54 −16
Original line number Diff line number Diff line
@@ -101,6 +101,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 */
@@ -253,8 +256,6 @@ static int ufshcd_setup_clocks(struct ufs_hba *hba, bool on);
static int ufshcd_uic_hibern8_exit(struct ufs_hba *hba);
static int ufshcd_uic_hibern8_enter(struct ufs_hba *hba);
static int ufshcd_host_reset_and_restore(struct ufs_hba *hba);
static int ufshcd_config_pwr_mode(struct ufs_hba *hba,
		struct ufs_pa_layer_attr *desired_pwr_mode);

static inline void ufshcd_enable_irq(struct ufs_hba *hba)
{
@@ -1113,15 +1114,12 @@ ufshcd_wait_for_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd)
 * @uic_cmd: UIC command
 *
 * Identical to ufshcd_send_uic_cmd() expect mutex. Must be called
 * with mutex held.
 * with mutex held and host_lock locked.
 * Returns 0 only if success.
 */
static int
__ufshcd_send_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd)
{
	int ret;
	unsigned long flags;

	if (!ufshcd_ready_for_uic_cmd(hba)) {
		dev_err(hba->dev,
			"Controller not ready to accept UIC commands\n");
@@ -1130,13 +1128,9 @@ __ufshcd_send_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd)

	init_completion(&uic_cmd->done);

	spin_lock_irqsave(hba->host->host_lock, flags);
	ufshcd_dispatch_uic_cmd(hba, uic_cmd);
	spin_unlock_irqrestore(hba->host->host_lock, flags);

	ret = ufshcd_wait_for_uic_cmd(hba, uic_cmd);

	return ret;
	return 0;
}

/**
@@ -1150,12 +1144,19 @@ static int
ufshcd_send_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd)
{
	int ret;
	unsigned long flags;

	ufshcd_hold(hba, false);
	pm_runtime_get_sync(hba->dev);
	mutex_lock(&hba->uic_cmd_mutex);
	spin_lock_irqsave(hba->host->host_lock, flags);
	ret = __ufshcd_send_uic_cmd(hba, uic_cmd);
	mutex_unlock(&hba->uic_cmd_mutex);
	spin_unlock_irqrestore(hba->host->host_lock, flags);
	if (!ret)
		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;
}
@@ -2414,16 +2415,53 @@ 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);

	/*
	 * 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);
	hba->uic_async_done = &uic_async_done;
		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) {
		dev_err(hba->dev,
			"pwr ctrl cmd 0x%x with mode 0x%x uic error %d\n",
			cmd->command, cmd->argument3, ret);
		goto out;
	}
	ret = ufshcd_wait_for_uic_cmd(hba, cmd);
	if (ret) {
		dev_err(hba->dev,
			"pwr ctrl cmd 0x%x with mode 0x%x uic error %d\n",
@@ -2452,7 +2490,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;
}
@@ -2622,7 +2660,7 @@ static int ufshcd_get_max_pwr_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 };
+3 −0
Original line number Diff line number Diff line
@@ -219,6 +219,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
@@ -646,6 +647,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