summaryrefslogtreecommitdiff
path: root/drivers/scsi/ufs/ufs-qcom-debugfs.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/scsi/ufs/ufs-qcom-debugfs.c')
-rw-r--r--drivers/scsi/ufs/ufs-qcom-debugfs.c366
1 files changed, 366 insertions, 0 deletions
diff --git a/drivers/scsi/ufs/ufs-qcom-debugfs.c b/drivers/scsi/ufs/ufs-qcom-debugfs.c
new file mode 100644
index 000000000000..8532439c392d
--- /dev/null
+++ b/drivers/scsi/ufs/ufs-qcom-debugfs.c
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) 2015, Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/debugfs.h>
+#include "ufs-qcom.h"
+#include "ufs-qcom-debugfs.h"
+#include "ufs-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)
+{
+ struct ufs_qcom_host *host = data;
+
+ if (!host)
+ return -EINVAL;
+
+ *attr_val = (u64)host->dbg_print_en;
+ return 0;
+}
+
+static int ufs_qcom_dbg_print_en_set(void *data, u64 attr_id)
+{
+ struct ufs_qcom_host *host = data;
+
+ if (!host)
+ return -EINVAL;
+
+ if (attr_id & ~UFS_QCOM_DBG_PRINT_ALL)
+ return -EINVAL;
+
+ host->dbg_print_en = (u32)attr_id;
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(ufs_qcom_dbg_print_en_ops,
+ ufs_qcom_dbg_print_en_read,
+ 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;
+
+ ret = simple_write_to_buffer(configuration, TESTBUS_CFG_BUFF_LINE_SIZE,
+ &buff_pos, ubuf, cnt);
+ if (ret < 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;
+
+ pm_runtime_get_sync(host->hba->dev);
+ ufshcd_hold(host->hba, false);
+ *attr_val = (u64)ufshcd_readl(host->hba, UFS_TEST_BUS);
+ ufshcd_release(host->hba, false);
+ pm_runtime_put_sync(host->hba->dev);
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(ufs_qcom_dbg_testbus_bus_ops,
+ ufs_qcom_dbg_testbus_bus_read,
+ NULL,
+ "%llu\n");
+
+static int ufs_qcom_dbg_dbg_regs_show(struct seq_file *file, void *data)
+{
+ struct ufs_qcom_host *host = (struct ufs_qcom_host *)file->private;
+ bool dbg_print_reg = !!(host->dbg_print_en &
+ UFS_QCOM_DBG_PRINT_REGS_EN);
+
+ pm_runtime_get_sync(host->hba->dev);
+ ufshcd_hold(host->hba, false);
+
+ /* Temporarily override the debug print enable */
+ host->dbg_print_en |= UFS_QCOM_DBG_PRINT_REGS_EN;
+ ufs_qcom_print_hw_debug_reg_all(host->hba, file, ufsdbg_pr_buf_to_std);
+ /* Restore previous debug print enable value */
+ if (!dbg_print_reg)
+ host->dbg_print_en &= ~UFS_QCOM_DBG_PRINT_REGS_EN;
+
+ ufshcd_release(host->hba, false);
+ pm_runtime_put_sync(host->hba->dev);
+
+ return 0;
+}
+
+static int ufs_qcom_dbg_dbg_regs_open(struct inode *inode,
+ struct file *file)
+{
+ return single_open(file, ufs_qcom_dbg_dbg_regs_show,
+ inode->i_private);
+}
+
+static const struct file_operations ufs_qcom_dbg_dbg_regs_desc = {
+ .open = ufs_qcom_dbg_dbg_regs_open,
+ .read = seq_read,
+};
+
+static int ufs_qcom_dbg_pm_qos_show(struct seq_file *file, void *data)
+{
+ struct ufs_qcom_host *host = (struct ufs_qcom_host *)file->private;
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(host->hba->host->host_lock, flags);
+
+ seq_printf(file, "enabled: %d\n", host->pm_qos.is_enabled);
+ for (i = 0; i < host->pm_qos.num_groups && host->pm_qos.groups; i++)
+ seq_printf(file,
+ "CPU Group #%d(mask=0x%lx): active_reqs=%d, state=%d, latency=%d\n",
+ i, host->pm_qos.groups[i].mask.bits[0],
+ host->pm_qos.groups[i].active_reqs,
+ host->pm_qos.groups[i].state,
+ host->pm_qos.groups[i].latency_us);
+
+ spin_unlock_irqrestore(host->hba->host->host_lock, flags);
+
+ return 0;
+}
+
+static int ufs_qcom_dbg_pm_qos_open(struct inode *inode,
+ struct file *file)
+{
+ return single_open(file, ufs_qcom_dbg_pm_qos_show, inode->i_private);
+}
+
+static const struct file_operations ufs_qcom_dbg_pm_qos_desc = {
+ .open = ufs_qcom_dbg_pm_qos_open,
+ .read = seq_read,
+};
+
+void ufs_qcom_dbg_add_debugfs(struct ufs_hba *hba, struct dentry *root)
+{
+ struct ufs_qcom_host *host;
+
+ if (!hba || !hba->priv) {
+ pr_err("%s: NULL host, exiting\n", __func__);
+ return;
+ }
+
+ host = hba->priv;
+ host->debugfs_files.debugfs_root = debugfs_create_dir("qcom", root);
+ if (IS_ERR(host->debugfs_files.debugfs_root))
+ /* Don't complain -- debugfs just isn't enabled */
+ goto err_no_root;
+ if (!host->debugfs_files.debugfs_root) {
+ /*
+ * Complain -- debugfs is enabled, but it failed to
+ * create the directory
+ */
+ dev_err(host->hba->dev,
+ "%s: NULL debugfs root directory, exiting", __func__);
+ goto err_no_root;
+ }
+
+ host->debugfs_files.dbg_print_en =
+ debugfs_create_file("dbg_print_en", S_IRUSR | S_IWUSR,
+ host->debugfs_files.debugfs_root, host,
+ &ufs_qcom_dbg_print_en_ops);
+ if (!host->debugfs_files.dbg_print_en) {
+ dev_err(host->hba->dev,
+ "%s: failed to create dbg_print_en debugfs entry\n",
+ __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;
+ }
+
+ host->debugfs_files.dbg_regs =
+ debugfs_create_file("debug-regs", S_IRUSR,
+ host->debugfs_files.debugfs_root, host,
+ &ufs_qcom_dbg_dbg_regs_desc);
+ if (!host->debugfs_files.dbg_regs) {
+ dev_err(host->hba->dev,
+ "%s: failed create dbg_regs debugfs entry\n",
+ __func__);
+ goto err;
+ }
+
+ host->debugfs_files.pm_qos =
+ debugfs_create_file("pm_qos", S_IRUSR,
+ host->debugfs_files.debugfs_root, host,
+ &ufs_qcom_dbg_pm_qos_desc);
+ if (!host->debugfs_files.dbg_regs) {
+ dev_err(host->hba->dev,
+ "%s: failed create dbg_regs debugfs entry\n",
+ __func__);
+ goto err;
+ }
+
+ return;
+
+err:
+ ufs_qcom_dbg_remove_debugfs(host);
+err_no_root:
+ dev_err(host->hba->dev, "%s: failed to initialize debugfs\n", __func__);
+}
+
+static void ufs_qcom_dbg_remove_debugfs(struct ufs_qcom_host *host)
+{
+ debugfs_remove_recursive(host->debugfs_files.debugfs_root);
+ host->debugfs_files.debugfs_root = NULL;
+}