diff options
Diffstat (limited to 'drivers/scsi/ufs/ufs-qcom-debugfs.c')
-rw-r--r-- | drivers/scsi/ufs/ufs-qcom-debugfs.c | 366 |
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; +} |