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

Commit 15b8dd65 authored by Dov Levenglick's avatar Dov Levenglick
Browse files

scsi: ufs-qcom: add debug prints for test bus



Adds support for configuring and reading the test bus and debug
registers. The configuration is controlled by debugfs. Reading can
be triggered either by debugfs or by the kernel code.

Change-Id: I943e3c1b1e383a91a8abbb8dab9714434c56b6f5
Signed-off-by: default avatarDov Levenglick <dovl@codeaurora.org>
parent 76d194dc
Loading
Loading
Loading
Loading
+179 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@
#include <linux/scsi/ufs/ufs-qcom.h>
#include "qcom-debugfs.h"

#define TESTBUS_CFG_BUFF_LINE_SIZE	sizeof("0xXY, 0xXY")

static void ufs_qcom_dbg_remove_debugfs(struct ufs_qcom_host *host);

static int ufs_qcom_dbg_print_en_read(void *data, u64 *attr_val)
@@ -48,6 +50,140 @@ DEFINE_SIMPLE_ATTRIBUTE(ufs_qcom_dbg_print_en_ops,
			ufs_qcom_dbg_print_en_set,
			"%llu\n");

static int ufs_qcom_dbg_testbus_en_read(void *data, u64 *attr_val)
{
	struct ufs_qcom_host *host = data;
	bool enabled;

	if (!host)
		return -EINVAL;

	enabled = !!(host->dbg_print_en & UFS_QCOM_DBG_PRINT_TEST_BUS_EN);
	*attr_val = (u64)enabled;
	return 0;
}

static int ufs_qcom_dbg_testbus_en_set(void *data, u64 attr_id)
{
	struct ufs_qcom_host *host = data;

	if (!host)
		return -EINVAL;

	if (!!attr_id)
		host->dbg_print_en |= UFS_QCOM_DBG_PRINT_TEST_BUS_EN;
	else
		host->dbg_print_en &= ~UFS_QCOM_DBG_PRINT_TEST_BUS_EN;

	return ufs_qcom_testbus_config(host);
}

DEFINE_SIMPLE_ATTRIBUTE(ufs_qcom_dbg_testbus_en_ops,
			ufs_qcom_dbg_testbus_en_read,
			ufs_qcom_dbg_testbus_en_set,
			"%llu\n");

static int ufs_qcom_dbg_testbus_cfg_show(struct seq_file *file, void *data)
{
	struct ufs_qcom_host *host = (struct ufs_qcom_host *)file->private;

	seq_printf(file , "Current configuration: major=%d, minor=%d\n\n",
			host->testbus.select_major, host->testbus.select_minor);

	/* Print usage */
	seq_puts(file,
		"To change the test-bus configuration, write 'MAJ,MIN' where:\n"
		"MAJ - major select\n"
		"MIN - minor select\n\n");
	return 0;
}

static ssize_t ufs_qcom_dbg_testbus_cfg_write(struct file *file,
				const char __user *ubuf, size_t cnt,
				loff_t *ppos)
{
	struct ufs_qcom_host *host = file->f_mapping->host->i_private;
	char configuration[TESTBUS_CFG_BUFF_LINE_SIZE] = {0};
	loff_t buff_pos = 0;
	char *comma;
	int ret = 0;
	int major;
	int minor;

	cnt = simple_write_to_buffer(configuration, TESTBUS_CFG_BUFF_LINE_SIZE,
		&buff_pos, ubuf, cnt);
	if (cnt < 0) {
		dev_err(host->hba->dev, "%s: failed to read user data\n",
			__func__);
		goto out;
	}

	comma = strnchr(configuration, TESTBUS_CFG_BUFF_LINE_SIZE, ',');
	if (!comma || comma == configuration) {
		dev_err(host->hba->dev,
			"%s: error in configuration of testbus\n", __func__);
		ret = -EINVAL;
		goto out;
	}

	if (sscanf(configuration, "%i,%i", &major, &minor) != 2) {
		dev_err(host->hba->dev,
			"%s: couldn't parse input to 2 numeric values\n",
			__func__);
		ret = -EINVAL;
		goto out;
	}

	host->testbus.select_major = (u8)major;
	host->testbus.select_minor = (u8)minor;

	/*
	 * Sanity check of the {major, minor} tuple is done in the
	 * config function
	 */
	ret = ufs_qcom_testbus_config(host);
	if (!ret)
		dev_dbg(host->hba->dev,
				"%s: New configuration: major=%d, minor=%d\n",
				__func__, host->testbus.select_major,
				host->testbus.select_minor);

out:
	return ret ? ret : cnt;
}

static int ufs_qcom_dbg_testbus_cfg_open(struct inode *inode, struct file *file)
{
	return single_open(file, ufs_qcom_dbg_testbus_cfg_show,
				inode->i_private);
}

static const struct file_operations ufs_qcom_dbg_testbus_cfg_desc = {
	.open		= ufs_qcom_dbg_testbus_cfg_open,
	.read		= seq_read,
	.write		= ufs_qcom_dbg_testbus_cfg_write,
};

static int ufs_qcom_dbg_testbus_bus_read(void *data, u64 *attr_val)
{
	struct ufs_qcom_host *host = data;

	if (!host)
		return -EINVAL;

	ufshcd_hold(host->hba, false);
	pm_runtime_get_sync(host->hba->dev);
	*attr_val = (u64)ufshcd_readl(host->hba, UFS_TEST_BUS);
	pm_runtime_put_sync(host->hba->dev);
	ufshcd_release(host->hba, false);
	return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(ufs_qcom_dbg_testbus_bus_ops,
			ufs_qcom_dbg_testbus_bus_read,
			NULL,
			"%llu\n");


void ufs_qcom_dbg_add_debugfs(struct ufs_hba *hba, struct dentry *root)
{
@@ -83,6 +219,49 @@ void ufs_qcom_dbg_add_debugfs(struct ufs_hba *hba, struct dentry *root)
			__func__);
		goto err;
	}

	host->debugfs_files.testbus = debugfs_create_dir("testbus",
					host->debugfs_files.debugfs_root);
	if (!host->debugfs_files.testbus) {
		dev_err(host->hba->dev,
			"%s: failed create testbus directory\n",
			__func__);
		goto err;
	}

	host->debugfs_files.testbus_en =
		debugfs_create_file("enable", S_IRUSR | S_IWUSR,
				    host->debugfs_files.testbus, host,
				    &ufs_qcom_dbg_testbus_en_ops);
	if (!host->debugfs_files.testbus_en) {
		dev_err(host->hba->dev,
			"%s: failed create testbus_en debugfs entry\n",
			__func__);
		goto err;
	}

	host->debugfs_files.testbus_cfg =
		debugfs_create_file("configuration", S_IRUSR | S_IWUSR,
				    host->debugfs_files.testbus, host,
				    &ufs_qcom_dbg_testbus_cfg_desc);
	if (!host->debugfs_files.testbus_cfg) {
		dev_err(host->hba->dev,
			"%s: failed create testbus_cfg debugfs entry\n",
			__func__);
		goto err;
	}

	host->debugfs_files.testbus_bus =
		debugfs_create_file("TEST_BUS", S_IRUSR,
				    host->debugfs_files.testbus, host,
				    &ufs_qcom_dbg_testbus_bus_ops);
	if (!host->debugfs_files.testbus_bus) {
		dev_err(host->hba->dev,
			"%s: failed create testbus_bus debugfs entry\n",
			__func__);
		goto err;
	}

	return;

err:
+157 −2
Original line number Diff line number Diff line
@@ -32,7 +32,24 @@
#include "ufs-qcom-ice.h"
#include "qcom-debugfs.h"

#define DEFAULT_UFS_QCOM_DBG_PRINT_EN	UFS_QCOM_DBG_PRINT_REGS_EN
#define UFS_QCOM_DEFAULT_DBG_PRINT_EN	\
	(UFS_QCOM_DBG_PRINT_REGS_EN | UFS_QCOM_DBG_PRINT_TEST_BUS_EN)

enum {
	TSTBUS_UAWM,
	TSTBUS_UARM,
	TSTBUS_TXUC,
	TSTBUS_RXUC,
	TSTBUS_DFC,
	TSTBUS_TRLUT,
	TSTBUS_TMRLUT,
	TSTBUS_OCSC,
	TSTBUS_UTP_HCI,
	TSTBUS_COMBINED,
	TSTBUS_WRAPPER,
	TSTBUS_UNIPRO,
	TSTBUS_MAX,
};

static struct ufs_qcom_host *ufs_qcom_hosts[MAX_UFS_QCOM_HOSTS];

@@ -41,6 +58,7 @@ static int ufs_qcom_get_bus_vote(struct ufs_qcom_host *host,
		const char *speed_mode);
static int ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote);
static int ufs_qcom_update_sec_cfg(struct ufs_hba *hba, bool restore_sec_cfg);
static void ufs_qcom_get_default_testbus_cfg(struct ufs_qcom_host *host);

static void ufs_qcom_dump_regs(struct ufs_hba *hba, int offset, int len,
		char *prefix)
@@ -1262,7 +1280,14 @@ static int ufs_qcom_init(struct ufs_hba *hba)
	if (hba->dev->id < MAX_UFS_QCOM_HOSTS)
		ufs_qcom_hosts[hba->dev->id] = host;

	host->dbg_print_en |= DEFAULT_UFS_QCOM_DBG_PRINT_EN;
	host->dbg_print_en |= UFS_QCOM_DEFAULT_DBG_PRINT_EN;
	ufs_qcom_get_default_testbus_cfg(host);
	err = ufs_qcom_testbus_config(host);
	if (err) {
		dev_warn(dev, "%s: failed to configure the testbus %d\n",
				__func__, err);
		err = 0;
	}

	goto out;

@@ -1404,6 +1429,135 @@ static void ufs_qcom_print_hw_debug_reg_all(struct ufs_hba *hba)
			"UFS_DBG_RD_REG_TMRLUT ");
}

static void ufs_qcom_enable_test_bus(struct ufs_qcom_host *host)
{
	if (host->dbg_print_en & UFS_QCOM_DBG_PRINT_TEST_BUS_EN)
		ufshcd_rmwl(host->hba, TEST_BUS_EN, TEST_BUS_EN, REG_UFS_CFG1);
	else
		ufshcd_rmwl(host->hba, TEST_BUS_EN, 0, REG_UFS_CFG1);
}

static void ufs_qcom_get_default_testbus_cfg(struct ufs_qcom_host *host)
{
	/* provide a legal default configuration */
	host->testbus.select_major = TSTBUS_UAWM;
	host->testbus.select_minor = 1;
}

static bool ufs_qcom_testbus_cfg_is_ok(struct ufs_qcom_host *host)
{
	if (host->testbus.select_major >= TSTBUS_MAX) {
		dev_err(host->hba->dev,
			"%s: UFS_CFG1[TEST_BUS_SEL} may not equal 0x%05X\n",
			__func__, host->testbus.select_major);
		return false;
	}

	/*
	 * Not performing check for each individual select_major
	 * mappings of select_minor, since there is no harm in
	 * configuring a non-existent select_minor
	 */
	if (host->testbus.select_major > 0x1F) {
		dev_err(host->hba->dev,
			"%s: 0x%05X is not a legal testbus option\n",
			__func__, host->testbus.select_minor);
		return false;
	}

	return true;
}

int ufs_qcom_testbus_config(struct ufs_qcom_host *host)
{
	int reg;
	int offset;
	u32 mask = TEST_BUS_SUB_SEL_MASK;

	if (!host)
		return -EINVAL;

	if (!ufs_qcom_testbus_cfg_is_ok(host))
		return -EPERM;

	switch (host->testbus.select_major) {
	case TSTBUS_UAWM:
		reg = UFS_TEST_BUS_CTRL_0;
		offset = 24;
		break;
	case TSTBUS_UARM:
		reg = UFS_TEST_BUS_CTRL_0;
		offset = 16;
		break;
	case TSTBUS_TXUC:
		reg = UFS_TEST_BUS_CTRL_0;
		offset = 8;
		break;
	case TSTBUS_RXUC:
		reg = UFS_TEST_BUS_CTRL_0;
		offset = 0;
		break;
	case TSTBUS_DFC:
		reg = UFS_TEST_BUS_CTRL_1;
		offset = 24;
		break;
	case TSTBUS_TRLUT:
		reg = UFS_TEST_BUS_CTRL_1;
		offset = 16;
		break;
	case TSTBUS_TMRLUT:
		reg = UFS_TEST_BUS_CTRL_1;
		offset = 8;
		break;
	case TSTBUS_OCSC:
		reg = UFS_TEST_BUS_CTRL_1;
		offset = 0;
		break;
	case TSTBUS_WRAPPER:
		reg = UFS_TEST_BUS_CTRL_2;
		offset = 16;
		break;
	case TSTBUS_COMBINED:
		reg = UFS_TEST_BUS_CTRL_2;
		offset = 8;
		break;
	case TSTBUS_UTP_HCI:
		reg = UFS_TEST_BUS_CTRL_2;
		offset = 0;
		break;
	case TSTBUS_UNIPRO:
		reg = UFS_UNIPRO_CFG;
		offset = 1;
		break;
	/*
	 * No need for a default case, since
	 * ufs_qcom_testbus_cfg_is_ok() checks that the configuration
	 * is legal
	 */
	}
	mask <<= offset;
	ufshcd_hold(host->hba, false);

	pm_runtime_get_sync(host->hba->dev);
	ufshcd_rmwl(host->hba, TEST_BUS_SEL,
		    (u32)host->testbus.select_major << 19,
		    REG_UFS_CFG1);
	ufshcd_rmwl(host->hba, mask,
		    (u32)host->testbus.select_minor << offset,
		    reg);
	ufs_qcom_enable_test_bus(host);

	pm_runtime_put_sync(host->hba->dev);
	ufshcd_release(host->hba, false);

	return 0;
}

static void ufs_qcom_testbus_read(struct ufs_hba *hba)
{
	ufs_qcom_dump_regs(hba, UFS_TEST_BUS, 1, "UFS_TEST_BUS ");
}

static void ufs_qcom_dump_dbg_regs(struct ufs_hba *hba)
{
	struct ufs_qcom_host *host = hba->priv;
@@ -1412,6 +1566,7 @@ static void ufs_qcom_dump_dbg_regs(struct ufs_hba *hba)
			"HCI Vendor Specific Registers ");

	ufs_qcom_print_hw_debug_reg_all(hba);
	ufs_qcom_testbus_read(hba);
	ufs_qcom_ice_print_regs(host);
}

+28 −2
Original line number Diff line number Diff line
@@ -61,6 +61,12 @@ enum {
	REG_UFS_CFG2                        = 0xE0,
	REG_UFS_HW_VERSION                  = 0xE4,

	UFS_TEST_BUS				= 0xE8,
	UFS_TEST_BUS_CTRL_0			= 0xEC,
	UFS_TEST_BUS_CTRL_1			= 0xF0,
	UFS_TEST_BUS_CTRL_2			= 0xF4,
	UFS_UNIPRO_CFG				= 0xF8,

	UFS_DBG_RD_REG_UAWM			= 0x100,
	UFS_DBG_RD_REG_UARM			= 0x200,
	UFS_DBG_RD_REG_TXUC			= 0x300,
@@ -76,6 +82,10 @@ enum {
	UFS_UFS_DBG_RD_EDTL_RAM			= 0x1900,
};

/* bit definitions for REG_UFS_CFG1 register */
#define TEST_BUS_EN		BIT(18)
#define TEST_BUS_SEL		GENMASK(22, 19)

/* bit definitions for REG_UFS_CFG2 register */
#define UAWM_HW_CGC_EN		(1 << 0)
#define UARM_HW_CGC_EN		(1 << 1)
@@ -86,6 +96,9 @@ enum {
#define TMRLUT_HW_CGC_EN	(1 << 6)
#define OCSC_HW_CGC_EN		(1 << 7)

/* bit definition for UFS_UFS_TEST_BUS_CTRL_n */
#define TEST_BUS_SUB_SEL_MASK	0x1F  /* All XXX_SEL fields are 5 bits wide */

#define REG_UFS_CFG2_CGC_EN_ALL (UAWM_HW_CGC_EN | UARM_HW_CGC_EN |\
				 TXUC_HW_CGC_EN | RXUC_HW_CGC_EN |\
				 DFC_HW_CGC_EN | TRLUT_HW_CGC_EN |\
@@ -122,10 +135,11 @@ struct ufs_qcom_phy_vreg {
/* QCOM UFS debug print bit mask */
#define UFS_QCOM_DBG_PRINT_REGS_EN	BIT(0)
#define UFS_QCOM_DBG_PRINT_ICE_REGS_EN	BIT(1)
#define UFS_QCOM_DBG_PRINT_TEST_BUS_EN	BIT(2)

#define UFS_QCOM_DBG_PRINT_ALL	\
	(UFS_QCOM_DBG_PRINT_REGS_EN | UFS_QCOM_DBG_PRINT_ICE_REGS_EN)

	(UFS_QCOM_DBG_PRINT_REGS_EN | UFS_QCOM_DBG_PRINT_ICE_REGS_EN | \
	 UFS_QCOM_DBG_PRINT_TEST_BUS_EN)

static inline void
ufs_qcom_get_controller_revision(struct ufs_hba *hba,
@@ -200,9 +214,18 @@ struct ufs_hw_version {
struct qcom_debugfs_files {
	struct dentry *debugfs_root;
	struct dentry *dbg_print_en;
	struct dentry *testbus;
	struct dentry *testbus_en;
	struct dentry *testbus_cfg;
	struct dentry *testbus_bus;
};
#endif

struct ufs_qcom_testbus {
	u8 select_major;
	u8 select_minor;
};

struct ufs_qcom_host {
	struct phy *generic_phy;
	struct ufs_hba *hba;
@@ -223,12 +246,15 @@ struct ufs_qcom_host {
#endif
	/* Bitmask for enabling debug prints */
	u32 dbg_print_en;
	struct ufs_qcom_testbus testbus;
};

#define ufs_qcom_is_link_off(hba) ufshcd_is_link_off(hba)
#define ufs_qcom_is_link_active(hba) ufshcd_is_link_active(hba)
#define ufs_qcom_is_link_hibern8(hba) ufshcd_is_link_hibern8(hba)

int ufs_qcom_testbus_config(struct ufs_qcom_host *host);

#define MAX_PROP_NAME              32
#define VDDA_PHY_MIN_UV            1000000
#define VDDA_PHY_MAX_UV            1000000