blob: 6b80fd4d7b80a3132cd1b56c137b8db3612bada4 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2022 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 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 GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 LOG layer HSSPI Protocol
*/
#include <linux/debugfs.h>
#include <linux/poll.h>
#include <linux/fsnotify.h>
#include <qmrom.h>
#include <qmrom_spi.h>
#include "qm35.h"
#include "debug.h"
#include "hsspi_test.h"
#if IS_ENABLED(CONFIG_QM35_SPI_DEBUG_FW)
extern void debug_rom_code_init(struct debug *debug);
#endif
static const struct file_operations debug_enable_fops;
static const struct file_operations debug_log_level_fops;
static const struct file_operations debug_test_hsspi_sleep_fops;
static void *priv_from_file(const struct file *filp)
{
return filp->f_path.dentry->d_inode->i_private;
}
static ssize_t debug_enable_write(struct file *filp, const char __user *buff,
size_t count, loff_t *off)
{
struct debug *debug;
u8 enabled;
debug = priv_from_file(filp);
if (kstrtou8_from_user(buff, count, 10, &enabled))
return -EFAULT;
if (debug->trace_ops)
debug->trace_ops->enable_set(debug, enabled == 1 ? 1 : 0);
else
return -ENOSYS;
return count;
}
static ssize_t debug_enable_read(struct file *filp, char __user *buff,
size_t count, loff_t *off)
{
char enabled[2];
struct debug *debug;
debug = priv_from_file(filp);
if (debug->trace_ops)
enabled[0] = debug->trace_ops->enable_get(debug) + '0';
else
return -ENOSYS;
enabled[1] = '\n';
return simple_read_from_buffer(buff, count, off, enabled,
sizeof(enabled));
}
static ssize_t debug_log_level_write(struct file *filp, const char __user *buff,
size_t count, loff_t *off)
{
u8 log_level = 0;
struct log_module *log_module;
log_module = priv_from_file(filp);
if (kstrtou8_from_user(buff, count, 10, &log_level))
return -EFAULT;
if (log_module->debug->trace_ops)
log_module->debug->trace_ops->level_set(log_module->debug,
log_module, log_level);
else
return -ENOSYS;
return count;
}
static ssize_t debug_log_level_read(struct file *filp, char __user *buff,
size_t count, loff_t *off)
{
char log_level[2];
struct log_module *log_module;
log_module = priv_from_file(filp);
if (log_module->debug->trace_ops)
log_level[0] = log_module->debug->trace_ops->level_get(
log_module->debug, log_module) +
'0';
else
return -ENOSYS;
log_level[1] = '\n';
return simple_read_from_buffer(buff, count, off, log_level,
sizeof(log_level));
}
static ssize_t debug_test_hsspi_sleep_write(struct file *filp,
const char __user *buff,
size_t count, loff_t *off)
{
int sleep_inter_frame_ms;
if (kstrtoint_from_user(buff, count, 10, &sleep_inter_frame_ms))
return -EFAULT;
hsspi_test_set_inter_frame_ms(sleep_inter_frame_ms);
return count;
}
static ssize_t debug_traces_read(struct file *filp, char __user *buff,
size_t count, loff_t *off)
{
char *entry;
rb_entry_size_t entry_size;
struct qm35_ctx *qm35_hdl;
uint16_t ret;
struct debug *debug;
debug = priv_from_file(filp);
qm35_hdl = container_of(debug, struct qm35_ctx, debug);
if (!debug->trace_ops)
return -ENOSYS;
entry_size = debug->trace_ops->trace_get_next_size(debug);
if (!entry_size) {
if (filp->f_flags & O_NONBLOCK)
return 0;
ret = wait_event_interruptible(
debug->wq,
(entry_size =
debug->trace_ops->trace_get_next_size(debug)));
if (ret)
return ret;
}
if (entry_size > count)
return -EMSGSIZE;
entry = debug->trace_ops->trace_get_next(debug, &entry_size);
if (!entry)
return 0;
ret = copy_to_user(buff, entry, entry_size);
kfree(entry);
return ret ? -EFAULT : entry_size;
}
static __poll_t debug_traces_poll(struct file *filp,
struct poll_table_struct *wait)
{
struct debug *debug;
__poll_t mask = 0;
debug = priv_from_file(filp);
poll_wait(filp, &debug->wq, wait);
if (debug->trace_ops && debug->trace_ops->trace_next_avail(debug))
mask |= POLLIN;
return mask;
}
static int debug_traces_open(struct inode *inodep, struct file *filep)
{
struct debug *debug;
debug = priv_from_file(filep);
mutex_lock(&debug->pv_filp_lock);
if (debug->pv_filp) {
mutex_unlock(&debug->pv_filp_lock);
return -EBUSY;
}
debug->pv_filp = filep;
if (debug->trace_ops)
debug->trace_ops->trace_reset(debug);
mutex_unlock(&debug->pv_filp_lock);
return 0;
}
static int debug_traces_release(struct inode *inodep, struct file *filep)
{
struct debug *debug;
debug = priv_from_file(filep);
mutex_lock(&debug->pv_filp_lock);
debug->pv_filp = NULL;
mutex_unlock(&debug->pv_filp_lock);
return 0;
}
static ssize_t debug_coredump_read(struct file *filep, char __user *buff,
size_t count, loff_t *off)
{
struct qm35_ctx *qm35_hdl;
struct debug *debug;
char *cd;
size_t cd_len = 0;
debug = priv_from_file(filep);
qm35_hdl = container_of(debug, struct qm35_ctx, debug);
if (!debug->coredump_ops)
return -ENOSYS;
cd = debug->coredump_ops->coredump_get(debug, &cd_len);
return simple_read_from_buffer(buff, count, off, cd, cd_len);
}
static ssize_t debug_coredump_write(struct file *filp, const char __user *buff,
size_t count, loff_t *off)
{
struct debug *debug;
u8 force;
debug = priv_from_file(filp);
if (kstrtou8_from_user(buff, count, 10, &force))
return -EFAULT;
if (debug->coredump_ops && force != 0)
debug->coredump_ops->coredump_force(debug);
else if (force == 0)
pr_warn("qm35: write non null value to force coredump\n");
else
return -ENOSYS;
return count;
}
static ssize_t debug_hw_reset_write(struct file *filp, const char __user *buff,
size_t count, loff_t *off)
{
struct qm35_ctx *qm35_hdl;
struct debug *debug;
int ret;
u8 reset;
debug = priv_from_file(filp);
qm35_hdl = container_of(debug, struct qm35_ctx, debug);
if (kstrtou8_from_user(buff, count, 10, &reset))
return -EFAULT;
ret = -1;
if (reset != 0) {
pr_info("qm35: resetting chip...\n");
ret = qm35_reset_sync(qm35_hdl);
} else
pr_warn("qm35: write non null value to force a hw reset\n");
if (ret)
return -ENOSYS;
return count;
}
static const struct file_operations debug_enable_fops = {
.owner = THIS_MODULE,
.write = debug_enable_write,
.read = debug_enable_read,
};
static const struct file_operations debug_log_level_fops = {
.owner = THIS_MODULE,
.write = debug_log_level_write,
.read = debug_log_level_read
};
static const struct file_operations debug_test_hsspi_sleep_fops = {
.owner = THIS_MODULE,
.write = debug_test_hsspi_sleep_write
};
static const struct file_operations debug_traces_fops = {
.owner = THIS_MODULE,
.open = debug_traces_open,
.release = debug_traces_release,
.read = debug_traces_read,
.poll = debug_traces_poll,
.llseek = no_llseek,
};
static const struct file_operations debug_coredump_fops = {
.owner = THIS_MODULE,
.read = debug_coredump_read,
.write = debug_coredump_write,
};
static const struct file_operations debug_hw_reset_fops = {
.owner = THIS_MODULE,
.write = debug_hw_reset_write,
};
int debug_create_module_entry(struct debug *debug,
struct log_module *log_module)
{
struct dentry *dir;
struct dentry *file;
dir = debugfs_create_dir(log_module->name, debug->fw_dir);
if (!dir) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/%s\n",
log_module->name);
return -1;
}
file = debugfs_create_file("log_level", 0644, dir, log_module,
&debug_log_level_fops);
if (!file) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/%s/log_level\n",
log_module->name);
return -1;
}
pr_info("qm35 debug: created /sys/kernel/debug/uwb0/%s/log_level\n",
log_module->name);
return 0;
}
void debug_new_trace_available(struct debug *debug)
{
if (debug->pv_filp)
fsnotify_modify(debug->pv_filp);
wake_up_interruptible(&debug->wq);
}
static int debug_devid_show(struct seq_file *s, void *unused)
{
struct debug *debug = (struct debug *)s->private;
uint16_t dev_id;
int rc;
if (debug->trace_ops && debug->trace_ops->get_dev_id) {
rc = debug->trace_ops->get_dev_id(debug, &dev_id);
if (rc < 0)
return -EIO;
seq_printf(s, "deca%04x\n", dev_id);
}
return 0;
}
static int debug_socid_show(struct seq_file *s, void *unused)
{
struct debug *debug = (struct debug *)s->private;
uint8_t soc_id[ROM_SOC_ID_LEN];
int rc;
if (debug->trace_ops && debug->trace_ops->get_soc_id) {
rc = debug->trace_ops->get_soc_id(debug, soc_id);
if (rc < 0)
return -EIO;
seq_printf(s, "%*phN\n", ROM_SOC_ID_LEN, soc_id);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(debug_devid);
DEFINE_SHOW_ATTRIBUTE(debug_socid);
void debug_soc_info_available(struct debug *debug)
{
struct dentry *file;
file = debugfs_create_file("dev_id", 0444, debug->chip_dir, debug,
&debug_devid_fops);
if (!file) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw/dev_id\n");
goto unregister;
}
file = debugfs_create_file("soc_id", 0444, debug->chip_dir, debug,
&debug_socid_fops);
if (!file) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw/soc_id\n");
goto unregister;
}
return;
unregister:
debugfs_remove_recursive(debug->chip_dir);
}
int debug_init_root(struct debug *debug, struct dentry *root)
{
debug->root_dir = debugfs_create_dir("uwb0", root);
if (!debug->root_dir) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0\n");
return -1;
}
return 0;
}
int debug_init(struct debug *debug)
{
struct dentry *file;
init_waitqueue_head(&debug->wq);
mutex_init(&debug->pv_filp_lock);
debug->pv_filp = NULL;
debug->fw_dir = debugfs_create_dir("fw", debug->root_dir);
if (!debug->fw_dir) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw\n");
goto unregister;
}
debug->chip_dir = debugfs_create_dir("chip", debug->root_dir);
if (!debug->chip_dir) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/chip\n");
goto unregister;
}
file = debugfs_create_file("hw_reset", 0444, debug->chip_dir, debug,
&debug_hw_reset_fops);
if (!file) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/chip/hw_reset\n");
goto unregister;
}
file = debugfs_create_file("enable", 0644, debug->fw_dir, debug,
&debug_enable_fops);
if (!file) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw/enable\n");
goto unregister;
}
file = debugfs_create_file("traces", 0444, debug->fw_dir, debug,
&debug_traces_fops);
if (!file) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw/traces\n");
goto unregister;
}
file = debugfs_create_file("coredump", 0444, debug->fw_dir, debug,
&debug_coredump_fops);
if (!file) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw/coredump\n");
goto unregister;
}
file = debugfs_create_file("test_sleep_hsspi_ms", 0200, debug->fw_dir,
debug, &debug_test_hsspi_sleep_fops);
if (!file) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw/test_sleep_hsspi\n");
goto unregister;
}
#if IS_ENABLED(CONFIG_QM35_SPI_DEBUG_FW)
debug_rom_code_init(debug);
#endif
return 0;
unregister:
debug_deinit(debug);
return -1;
}
void debug_deinit(struct debug *debug)
{
wake_up_interruptible(&debug->wq);
debugfs_remove_recursive(debug->root_dir);
}