blob: ecbe307f2d9774ee82e52c81255d87f645f5673d [file] [log] [blame]
/*
* Copyright (c) 2015-2018 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;
int ret = 0;
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;
pm_runtime_get_sync(host->hba->dev);
ufshcd_hold(host->hba, false);
ret = ufs_qcom_testbus_config(host);
ufshcd_release(host->hba, false);
pm_runtime_put_sync(host->hba->dev);
return ret;
}
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;
unsigned long flags;
struct ufs_hba *hba = host->hba;
ret = simple_write_to_buffer(configuration,
TESTBUS_CFG_BUFF_LINE_SIZE - 1,
&buff_pos, ubuf, cnt);
if (ret < 0) {
dev_err(host->hba->dev, "%s: failed to read user data\n",
__func__);
goto out;
}
configuration[ret] = '\0';
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;
}
if (!ufs_qcom_testbus_cfg_is_ok(host, major, minor)) {
ret = -EPERM;
goto out;
}
spin_lock_irqsave(hba->host->host_lock, flags);
host->testbus.select_major = (u8)major;
host->testbus.select_minor = (u8)minor;
spin_unlock_irqrestore(hba->host->host_lock, flags);
/*
* Sanity check of the {major, minor} tuple is done in the
* config function
*/
pm_runtime_get_sync(host->hba->dev);
ufshcd_hold(host->hba, false);
ret = ufs_qcom_testbus_config(host);
ufshcd_release(host->hba, false);
pm_runtime_put_sync(host->hba->dev);
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,
.release = single_release,
};
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,
.release = single_release,
};
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,
.release = single_release,
};
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;
}