/* * 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 #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; 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 */ 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, }; 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; }