blob: 46f64d28db13bd03363909f16ef1d04b87c054a9 [file] [log] [blame]
/* Copyright (c) 2015-2017, The 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.
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/debugfs.h>
#include "sde_rotator_debug.h"
#include "sde_rotator_base.h"
#include "sde_rotator_core.h"
#include "sde_rotator_dev.h"
#include "sde_rotator_trace.h"
#ifdef CONFIG_MSM_SDE_ROTATOR_EVTLOG_DEBUG
#define SDE_EVTLOG_DEFAULT_ENABLE 1
#else
#define SDE_EVTLOG_DEFAULT_ENABLE 0
#endif
#define SDE_EVTLOG_DEFAULT_PANIC 1
#define SDE_EVTLOG_DEFAULT_REGDUMP SDE_ROT_DBG_DUMP_IN_MEM
#define SDE_EVTLOG_DEFAULT_VBIF_DBGBUSDUMP SDE_ROT_DBG_DUMP_IN_MEM
#define SDE_EVTLOG_DEFAULT_ROT_DBGBUSDUMP SDE_ROT_DBG_DUMP_IN_MEM
/*
* evtlog will print this number of entries when it is called through
* sysfs node or panic. This prevents kernel log from evtlog message
* flood.
*/
#define SDE_ROT_EVTLOG_PRINT_ENTRY 256
/*
* evtlog keeps this number of entries in memory for debug purpose. This
* number must be greater than print entry to prevent out of bound evtlog
* entry array access.
*/
#define SDE_ROT_EVTLOG_ENTRY (SDE_ROT_EVTLOG_PRINT_ENTRY * 4)
#define SDE_ROT_EVTLOG_MAX_DATA 15
#define SDE_ROT_EVTLOG_BUF_MAX 512
#define SDE_ROT_EVTLOG_BUF_ALIGN 32
#define SDE_ROT_DEBUG_BASE_MAX 10
#define SDE_ROT_DEFAULT_BASE_REG_CNT 0x100
#define GROUP_BYTES 4
#define ROW_BYTES 16
#define SDE_ROT_TEST_MASK(id, tp) ((id << 4) | (tp << 1) | BIT(0))
static DEFINE_SPINLOCK(sde_rot_xlock);
/*
* tlog - EVTLOG entry structure
* @counter - EVTLOG entriy counter
* @time - timestamp of EVTLOG entry
* @name - function name of EVTLOG entry
* @line - line number of EVTLOG entry
* @data - EVTLOG data contents
* @data_cnt - number of data contents
* @pid - pid of current calling thread
*/
struct tlog {
u32 counter;
s64 time;
const char *name;
int line;
u32 data[SDE_ROT_EVTLOG_MAX_DATA];
u32 data_cnt;
int pid;
};
/*
* sde_rot_dbg_evtlog - EVTLOG debug data structure
* @logs - EVTLOG entries
* @first - first entry index in the EVTLOG
* @last - last entry index in the EVTLOG
* @curr - curr entry index in the EVTLOG
* @evtlog - EVTLOG debugfs handle
* @evtlog_enable - boolean indicates EVTLOG enable/disable
* @panic_on_err - boolean indicates issue panic after EVTLOG dump
* @enable_reg_dump - control in-log/memory dump for rotator registers
* @enable_vbif_dbgbus_dump - control in-log/memory dump for VBIF debug bus
* @enable_rot_dbgbus_dump - control in-log/memroy dump for rotator debug bus
* @evtlog_dump_work - schedule work strucutre for timeout handler
* @work_dump_reg - storage for register dump control in schedule work
* @work_panic - storage for panic control in schedule work
* @work_vbif_dbgbus - storage for VBIF debug bus control in schedule work
* @work_rot_dbgbus - storage for rotator debug bus control in schedule work
* @nrt_vbif_dbgbus_dump - memory buffer for VBIF debug bus dumping
* @rot_dbgbus_dump - memory buffer for rotator debug bus dumping
* @reg_dump_array - memory buffer for rotator registers dumping
*/
struct sde_rot_dbg_evtlog {
struct tlog logs[SDE_ROT_EVTLOG_ENTRY];
u32 first;
u32 last;
u32 curr;
struct dentry *evtlog;
u32 evtlog_enable;
u32 panic_on_err;
u32 enable_reg_dump;
u32 enable_vbif_dbgbus_dump;
u32 enable_rot_dbgbus_dump;
struct work_struct evtlog_dump_work;
bool work_dump_reg;
bool work_panic;
bool work_vbif_dbgbus;
bool work_rot_dbgbus;
u32 *nrt_vbif_dbgbus_dump; /* address for the nrt vbif debug bus dump */
u32 *rot_dbgbus_dump;
u32 *reg_dump_array[SDE_ROT_DEBUG_BASE_MAX];
} sde_rot_dbg_evtlog;
static void sde_rot_dump_debug_bus(u32 bus_dump_flag, u32 **dump_mem)
{
struct sde_rot_data_type *mdata = sde_rot_get_mdata();
bool in_log, in_mem;
u32 *dump_addr = NULL;
u32 status = 0;
struct sde_rot_debug_bus *head;
phys_addr_t phys = 0;
int i;
u32 offset;
void __iomem *base;
in_log = (bus_dump_flag & SDE_ROT_DBG_DUMP_IN_LOG);
in_mem = (bus_dump_flag & SDE_ROT_DBG_DUMP_IN_MEM);
base = mdata->sde_io.base;
if (!base || !mdata->rot_dbg_bus || !mdata->rot_dbg_bus_size)
return;
pr_info("======== SDE Rotator Debug bus DUMP =========\n");
if (in_mem) {
if (!(*dump_mem))
*dump_mem = dma_alloc_coherent(&mdata->pdev->dev,
mdata->rot_dbg_bus_size * 4 * sizeof(u32),
&phys, GFP_KERNEL);
if (*dump_mem) {
dump_addr = *dump_mem;
pr_info("%s: start_addr:0x%pK end_addr:0x%pK\n",
__func__, dump_addr,
dump_addr + (u32)mdata->rot_dbg_bus_size * 16);
} else {
in_mem = false;
pr_err("dump_mem: allocation fails\n");
}
}
sde_smmu_ctrl(1);
for (i = 0; i < mdata->rot_dbg_bus_size; i++) {
head = mdata->rot_dbg_bus + i;
writel_relaxed(SDE_ROT_TEST_MASK(head->block_id, head->test_id),
base + head->wr_addr);
wmb(); /* make sure test bits were written */
offset = head->wr_addr + 0x4;
status = readl_relaxed(base + offset);
if (in_log)
pr_err("waddr=0x%x blk=%d tst=%d val=0x%x\n",
head->wr_addr, head->block_id, head->test_id,
status);
if (dump_addr && in_mem) {
dump_addr[i*4] = head->wr_addr;
dump_addr[i*4 + 1] = head->block_id;
dump_addr[i*4 + 2] = head->test_id;
dump_addr[i*4 + 3] = status;
}
/* Disable debug bus once we are done */
writel_relaxed(0, base + head->wr_addr);
}
sde_smmu_ctrl(0);
pr_info("========End Debug bus=========\n");
}
/*
* sde_rot_evtlog_is_enabled - helper function for checking EVTLOG
* enable/disable
* @flag - EVTLOG option flag
*/
static inline bool sde_rot_evtlog_is_enabled(u32 flag)
{
return (flag & sde_rot_dbg_evtlog.evtlog_enable) ||
(flag == SDE_ROT_EVTLOG_ALL &&
sde_rot_dbg_evtlog.evtlog_enable);
}
/*
* __vbif_debug_bus - helper function for VBIF debug bus dump
* @head - VBIF debug bus data structure
* @vbif_base - VBIF IO mapped address
* @dump_addr - output buffer for memory dump option
* @in_log - boolean indicates in-log dump option
*/
static void __vbif_debug_bus(struct sde_rot_vbif_debug_bus *head,
void __iomem *vbif_base, u32 *dump_addr, bool in_log)
{
int i, j;
u32 val;
if (!dump_addr && !in_log)
return;
for (i = 0; i < head->block_cnt; i++) {
writel_relaxed(1 << (i + head->bit_offset),
vbif_base + head->block_bus_addr);
/* make sure that current bus blcok enable */
wmb();
for (j = 0; j < head->test_pnt_cnt; j++) {
writel_relaxed(j, vbif_base + head->block_bus_addr + 4);
/* make sure that test point is enabled */
wmb();
val = readl_relaxed(vbif_base + MMSS_VBIF_TEST_BUS_OUT);
if (dump_addr) {
*dump_addr++ = head->block_bus_addr;
*dump_addr++ = i;
*dump_addr++ = j;
*dump_addr++ = val;
}
if (in_log)
pr_err("testpoint:%x arb/xin id=%d index=%d val=0x%x\n",
head->block_bus_addr, i, j, val);
}
}
}
/*
* sde_rot_dump_vbif_debug_bus - VBIF debug bus dump
* @bus_dump_flag - dump flag controlling in-log/memory dump option
* @dump_mem - output buffer for memory dump location
*/
static void sde_rot_dump_vbif_debug_bus(u32 bus_dump_flag,
u32 **dump_mem)
{
struct sde_rot_data_type *mdata = sde_rot_get_mdata();
bool in_log, in_mem;
u32 *dump_addr = NULL;
u32 value;
struct sde_rot_vbif_debug_bus *head;
phys_addr_t phys = 0;
int i, list_size = 0;
void __iomem *vbif_base;
struct sde_rot_vbif_debug_bus *dbg_bus;
u32 bus_size;
pr_info("======== NRT VBIF Debug bus DUMP =========\n");
vbif_base = mdata->vbif_nrt_io.base;
dbg_bus = mdata->nrt_vbif_dbg_bus;
bus_size = mdata->nrt_vbif_dbg_bus_size;
if (!vbif_base || !dbg_bus || !bus_size)
return;
/* allocate memory for each test point */
for (i = 0; i < bus_size; i++) {
head = dbg_bus + i;
list_size += (head->block_cnt * head->test_pnt_cnt);
}
/* 4 bytes * 4 entries for each test point*/
list_size *= 16;
in_log = (bus_dump_flag & SDE_ROT_DBG_DUMP_IN_LOG);
in_mem = (bus_dump_flag & SDE_ROT_DBG_DUMP_IN_MEM);
if (in_mem) {
if (!(*dump_mem))
*dump_mem = dma_alloc_coherent(&mdata->pdev->dev,
list_size, &phys, GFP_KERNEL);
if (*dump_mem) {
dump_addr = *dump_mem;
pr_info("%s: start_addr:0x%pK end_addr:0x%pK\n",
__func__, dump_addr, dump_addr + list_size);
} else {
in_mem = false;
pr_err("dump_mem: allocation fails\n");
}
}
sde_smmu_ctrl(1);
value = readl_relaxed(vbif_base + MMSS_VBIF_CLKON);
writel_relaxed(value | BIT(1), vbif_base + MMSS_VBIF_CLKON);
/* make sure that vbif core is on */
wmb();
for (i = 0; i < bus_size; i++) {
head = dbg_bus + i;
writel_relaxed(0, vbif_base + head->disable_bus_addr);
writel_relaxed(BIT(0), vbif_base + MMSS_VBIF_TEST_BUS_OUT_CTRL);
/* make sure that other bus is off */
wmb();
__vbif_debug_bus(head, vbif_base, dump_addr, in_log);
if (dump_addr)
dump_addr += (head->block_cnt * head->test_pnt_cnt * 4);
}
sde_smmu_ctrl(0);
pr_info("========End VBIF Debug bus=========\n");
}
/*
* sde_rot_dump_reg - helper function for dumping rotator register set content
* @dump_name - register set name
* @reg_dump_flag - dumping flag controlling in-log/memory dump location
* @access - access type, sde registers or vbif registers
* @addr - starting address offset for dumping
* @len - range of the register set
* @dump_mem - output buffer for memory dump location option
*/
void sde_rot_dump_reg(const char *dump_name, u32 reg_dump_flag,
enum sde_rot_regdump_access access, u32 addr,
int len, u32 **dump_mem)
{
struct sde_rot_data_type *mdata = sde_rot_get_mdata();
bool in_log, in_mem;
u32 *dump_addr = NULL;
phys_addr_t phys = 0;
int i;
void __iomem *base;
in_log = (reg_dump_flag & SDE_ROT_DBG_DUMP_IN_LOG);
in_mem = (reg_dump_flag & SDE_ROT_DBG_DUMP_IN_MEM);
pr_debug("reg_dump_flag=%d in_log=%d in_mem=%d\n",
reg_dump_flag, in_log, in_mem);
if (len % 16)
len += 16;
len /= 16;
if (in_mem) {
if (!(*dump_mem))
*dump_mem = dma_alloc_coherent(&mdata->pdev->dev,
len * 16, &phys, GFP_KERNEL);
if (*dump_mem) {
dump_addr = *dump_mem;
pr_info("%s: start_addr:0x%p end_addr:0x%p reg_addr=0x%X\n",
dump_name, dump_addr, dump_addr + (u32)len * 16,
addr);
} else {
in_mem = false;
pr_err("dump_mem: kzalloc fails!\n");
}
}
base = mdata->sde_io.base;
/*
* VBIF NRT base handling
*/
if (access == SDE_ROT_REGDUMP_VBIF)
base = mdata->vbif_nrt_io.base;
for (i = 0; i < len; i++) {
u32 x0, x4, x8, xc;
x0 = readl_relaxed(base + addr+0x0);
x4 = readl_relaxed(base + addr+0x4);
x8 = readl_relaxed(base + addr+0x8);
xc = readl_relaxed(base + addr+0xc);
if (in_log)
pr_info("0x%08X : %08x %08x %08x %08x\n",
addr, x0, x4, x8, xc);
if (dump_addr && in_mem) {
dump_addr[i*4] = x0;
dump_addr[i*4 + 1] = x4;
dump_addr[i*4 + 2] = x8;
dump_addr[i*4 + 3] = xc;
}
addr += 16;
}
}
/*
* sde_rot_dump_reg_all - dumping all SDE rotator registers
*/
static void sde_rot_dump_reg_all(void)
{
struct sde_rot_data_type *mdata = sde_rot_get_mdata();
struct sde_rot_regdump *head, *regdump;
u32 regdump_size;
int i;
regdump = mdata->regdump;
regdump_size = mdata->regdump_size;
if (!regdump || !regdump_size)
return;
/* Enable clock to rotator if not yet enabled */
sde_smmu_ctrl(1);
for (i = 0; (i < regdump_size) && (i < SDE_ROT_DEBUG_BASE_MAX); i++) {
head = &regdump[i];
if (head->access == SDE_ROT_REGDUMP_WRITE) {
writel_relaxed(1, mdata->sde_io.base + head->offset);
/* Make sure write go through */
wmb();
} else {
sde_rot_dump_reg(head->name,
sde_rot_dbg_evtlog.enable_reg_dump,
head->access,
head->offset, head->len,
&sde_rot_dbg_evtlog.reg_dump_array[i]);
}
}
/* Disable rotator clock */
sde_smmu_ctrl(0);
}
/*
* __sde_rot_evtlog_dump_calc_range - calculate dump range for EVTLOG
*/
static bool __sde_rot_evtlog_dump_calc_range(void)
{
static u32 next;
bool need_dump = true;
unsigned long flags;
struct sde_rot_dbg_evtlog *evtlog = &sde_rot_dbg_evtlog;
spin_lock_irqsave(&sde_rot_xlock, flags);
evtlog->first = next;
if (evtlog->last == evtlog->first) {
need_dump = false;
goto dump_exit;
}
if (evtlog->last < evtlog->first) {
evtlog->first %= SDE_ROT_EVTLOG_ENTRY;
if (evtlog->last < evtlog->first)
evtlog->last += SDE_ROT_EVTLOG_ENTRY;
}
if ((evtlog->last - evtlog->first) > SDE_ROT_EVTLOG_PRINT_ENTRY) {
pr_warn("evtlog buffer overflow before dump: %d\n",
evtlog->last - evtlog->first);
evtlog->first = evtlog->last - SDE_ROT_EVTLOG_PRINT_ENTRY;
}
next = evtlog->first + 1;
dump_exit:
spin_unlock_irqrestore(&sde_rot_xlock, flags);
return need_dump;
}
/*
* sde_rot_evtlog_dump_entry - helper function for EVTLOG content dumping
* @evtlog_buf: EVTLOG dump output buffer
* @evtlog_buf_size: EVTLOG output buffer size
*/
static ssize_t sde_rot_evtlog_dump_entry(char *evtlog_buf,
ssize_t evtlog_buf_size)
{
int i;
ssize_t off = 0;
struct tlog *log, *prev_log;
unsigned long flags;
spin_lock_irqsave(&sde_rot_xlock, flags);
log = &sde_rot_dbg_evtlog.logs[sde_rot_dbg_evtlog.first %
SDE_ROT_EVTLOG_ENTRY];
prev_log = &sde_rot_dbg_evtlog.logs[(sde_rot_dbg_evtlog.first - 1) %
SDE_ROT_EVTLOG_ENTRY];
off = snprintf((evtlog_buf + off), (evtlog_buf_size - off), "%s:%-4d",
log->name, log->line);
if (off < SDE_ROT_EVTLOG_BUF_ALIGN) {
memset((evtlog_buf + off), 0x20,
(SDE_ROT_EVTLOG_BUF_ALIGN - off));
off = SDE_ROT_EVTLOG_BUF_ALIGN;
}
off += snprintf((evtlog_buf + off), (evtlog_buf_size - off),
"=>[%-8d:%-11llu:%9llu][%-4d]:", sde_rot_dbg_evtlog.first,
log->time, (log->time - prev_log->time), log->pid);
for (i = 0; i < log->data_cnt; i++)
off += snprintf((evtlog_buf + off), (evtlog_buf_size - off),
"%x ", log->data[i]);
off += snprintf((evtlog_buf + off), (evtlog_buf_size - off), "\n");
spin_unlock_irqrestore(&sde_rot_xlock, flags);
return off;
}
/*
* sde_rot_evtlog_dump_all - Dumping all content in EVTLOG buffer
*/
static void sde_rot_evtlog_dump_all(void)
{
char evtlog_buf[SDE_ROT_EVTLOG_BUF_MAX];
while (__sde_rot_evtlog_dump_calc_range()) {
sde_rot_evtlog_dump_entry(evtlog_buf, SDE_ROT_EVTLOG_BUF_MAX);
pr_info("%s", evtlog_buf);
}
}
/*
* sde_rot_evtlog_dump_open - debugfs open handler for evtlog dump
* @inode: debugfs inode
* @file: file handler
*/
static int sde_rot_evtlog_dump_open(struct inode *inode, struct file *file)
{
/* non-seekable */
file->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE);
file->private_data = inode->i_private;
return 0;
}
/*
* sde_rot_evtlog_dump_read - debugfs read handler for evtlog dump
* @file: file handler
* @buff: user buffer content for debugfs
* @count: size of user buffer
* @ppos: position offset of user buffer
*/
static ssize_t sde_rot_evtlog_dump_read(struct file *file, char __user *buff,
size_t count, loff_t *ppos)
{
ssize_t len = 0;
char evtlog_buf[SDE_ROT_EVTLOG_BUF_MAX];
if (__sde_rot_evtlog_dump_calc_range()) {
len = sde_rot_evtlog_dump_entry(evtlog_buf,
SDE_ROT_EVTLOG_BUF_MAX);
if (copy_to_user(buff, evtlog_buf, len))
return -EFAULT;
*ppos += len;
}
return len;
}
/*
* sde_rot_evtlog_dump_write - debugfs write handler for evtlog dump
* @file: file handler
* @user_buf: user buffer content from debugfs
* @count: size of user buffer
* @ppos: position offset of user buffer
*/
static ssize_t sde_rot_evtlog_dump_write(struct file *file,
const char __user *user_buf, size_t count, loff_t *ppos)
{
sde_rot_evtlog_dump_all();
sde_rot_dump_reg_all();
if (sde_rot_dbg_evtlog.panic_on_err)
panic("evtlog_dump_write");
return count;
}
/*
* sde_rot_evtlog_dump_helper - helper function for evtlog dump
* @dead: boolean indicates panic after dump
* @panic_name: Panic signature name show up in log
* @dump_rot: boolean indicates rotator register dump
* @dump_vbif_debug_bus: boolean indicates VBIF debug bus dump
*/
static void sde_rot_evtlog_dump_helper(bool dead, const char *panic_name,
bool dump_rot, bool dump_vbif_debug_bus, bool dump_rot_debug_bus)
{
sde_rot_evtlog_dump_all();
if (dump_rot_debug_bus)
sde_rot_dump_debug_bus(
sde_rot_dbg_evtlog.enable_rot_dbgbus_dump,
&sde_rot_dbg_evtlog.rot_dbgbus_dump);
if (dump_vbif_debug_bus)
sde_rot_dump_vbif_debug_bus(
sde_rot_dbg_evtlog.enable_vbif_dbgbus_dump,
&sde_rot_dbg_evtlog.nrt_vbif_dbgbus_dump);
/*
* Rotator registers always dump last
*/
if (dump_rot)
sde_rot_dump_reg_all();
if (dead)
panic(panic_name);
}
/*
* sde_rot_evtlog_debug_work - schedule work function for evtlog dump
* @work: schedule work structure
*/
static void sde_rot_evtlog_debug_work(struct work_struct *work)
{
sde_rot_evtlog_dump_helper(
sde_rot_dbg_evtlog.work_panic,
"evtlog_workitem",
sde_rot_dbg_evtlog.work_dump_reg,
sde_rot_dbg_evtlog.work_vbif_dbgbus,
sde_rot_dbg_evtlog.work_rot_dbgbus);
}
/*
* sde_rot_dump_panic - Issue evtlog dump and generic panic
*/
void sde_rot_dump_panic(bool do_panic)
{
sde_rot_evtlog_dump_all();
sde_rot_dump_reg_all();
if (do_panic)
panic("sde_rotator");
}
/*
* sde_rot_evtlog_tout_handler - log dump timeout handler
* @queue: boolean indicate putting log dump into queue
* @name: function name having timeout
*/
void sde_rot_evtlog_tout_handler(bool queue, const char *name, ...)
{
int i;
bool dead = false;
bool dump_rot = false;
bool dump_vbif_dbgbus = false;
bool dump_rot_dbgbus = false;
char *blk_name = NULL;
va_list args;
if (!sde_rot_evtlog_is_enabled(SDE_ROT_EVTLOG_DEFAULT))
return;
if (queue && work_pending(&sde_rot_dbg_evtlog.evtlog_dump_work))
return;
va_start(args, name);
for (i = 0; i < SDE_ROT_EVTLOG_MAX_DATA; i++) {
blk_name = va_arg(args, char*);
if (IS_ERR_OR_NULL(blk_name))
break;
if (!strcmp(blk_name, "rot"))
dump_rot = true;
if (!strcmp(blk_name, "vbif_dbg_bus"))
dump_vbif_dbgbus = true;
if (!strcmp(blk_name, "rot_dbg_bus"))
dump_rot_dbgbus = true;
if (!strcmp(blk_name, "panic"))
dead = true;
}
va_end(args);
if (queue) {
/* schedule work to dump later */
sde_rot_dbg_evtlog.work_panic = dead;
sde_rot_dbg_evtlog.work_dump_reg = dump_rot;
sde_rot_dbg_evtlog.work_vbif_dbgbus = dump_vbif_dbgbus;
sde_rot_dbg_evtlog.work_rot_dbgbus = dump_rot_dbgbus;
schedule_work(&sde_rot_dbg_evtlog.evtlog_dump_work);
} else {
sde_rot_evtlog_dump_helper(dead, name, dump_rot,
dump_vbif_dbgbus, dump_rot_dbgbus);
}
}
/*
* sde_rot_evtlog - log contents into memory for dump analysis
* @name: Name of function calling evtlog
* @line: line number of calling function
* @flag: Log control flag
*/
void sde_rot_evtlog(const char *name, int line, int flag, ...)
{
unsigned long flags;
int i, val = 0;
va_list args;
struct tlog *log;
if (!sde_rot_evtlog_is_enabled(flag))
return;
spin_lock_irqsave(&sde_rot_xlock, flags);
log = &sde_rot_dbg_evtlog.logs[sde_rot_dbg_evtlog.curr];
log->time = ktime_to_us(ktime_get());
log->name = name;
log->line = line;
log->data_cnt = 0;
log->pid = current->pid;
va_start(args, flag);
for (i = 0; i < SDE_ROT_EVTLOG_MAX_DATA; i++) {
val = va_arg(args, int);
if (val == SDE_ROT_DATA_LIMITER)
break;
log->data[i] = val;
}
va_end(args);
log->data_cnt = i;
sde_rot_dbg_evtlog.curr =
(sde_rot_dbg_evtlog.curr + 1) % SDE_ROT_EVTLOG_ENTRY;
sde_rot_dbg_evtlog.last++;
trace_sde_rot_evtlog(name, line, log->data_cnt, log->data);
spin_unlock_irqrestore(&sde_rot_xlock, flags);
}
/*
* sde_rotator_stat_show - Show statistics on read to this debugfs file
* @s: Pointer to sequence file structure
* @data: Pointer to private data structure
*/
static int sde_rotator_stat_show(struct seq_file *s, void *data)
{
int i, offset;
struct sde_rotator_device *rot_dev = s->private;
struct sde_rotator_statistics *stats = &rot_dev->stats;
u64 count = stats->count;
int num_events;
s64 proc_max, proc_min, proc_avg;
s64 swoh_max, swoh_min, swoh_avg;
proc_max = 0;
proc_min = S64_MAX;
proc_avg = 0;
swoh_max = 0;
swoh_min = S64_MAX;
swoh_avg = 0;
if (count > SDE_ROTATOR_NUM_EVENTS) {
num_events = SDE_ROTATOR_NUM_EVENTS;
offset = count % SDE_ROTATOR_NUM_EVENTS;
} else {
num_events = count;
offset = 0;
}
for (i = 0; i < num_events; i++) {
int k = (offset + i) % SDE_ROTATOR_NUM_EVENTS;
ktime_t *ts = stats->ts[k];
ktime_t start_time =
ktime_before(ts[SDE_ROTATOR_TS_SRCQB],
ts[SDE_ROTATOR_TS_DSTQB]) ?
ts[SDE_ROTATOR_TS_SRCQB] :
ts[SDE_ROTATOR_TS_DSTQB];
s64 proc_time =
ktime_to_us(ktime_sub(ts[SDE_ROTATOR_TS_RETIRE],
start_time));
s64 sw_overhead_time =
ktime_to_us(ktime_sub(ts[SDE_ROTATOR_TS_FLUSH],
start_time));
seq_printf(s,
"s:%d sq:%lld dq:%lld fe:%lld q:%lld c:%lld st:%lld fl:%lld d:%lld sdq:%lld ddq:%lld t:%lld oht:%lld\n",
i,
ktime_to_us(ktime_sub(ts[SDE_ROTATOR_TS_FENCE],
ts[SDE_ROTATOR_TS_SRCQB])),
ktime_to_us(ktime_sub(ts[SDE_ROTATOR_TS_FENCE],
ts[SDE_ROTATOR_TS_DSTQB])),
ktime_to_us(ktime_sub(ts[SDE_ROTATOR_TS_QUEUE],
ts[SDE_ROTATOR_TS_FENCE])),
ktime_to_us(ktime_sub(ts[SDE_ROTATOR_TS_COMMIT],
ts[SDE_ROTATOR_TS_QUEUE])),
ktime_to_us(ktime_sub(ts[SDE_ROTATOR_TS_START],
ts[SDE_ROTATOR_TS_COMMIT])),
ktime_to_us(ktime_sub(ts[SDE_ROTATOR_TS_FLUSH],
ts[SDE_ROTATOR_TS_START])),
ktime_to_us(ktime_sub(ts[SDE_ROTATOR_TS_DONE],
ts[SDE_ROTATOR_TS_FLUSH])),
ktime_to_us(ktime_sub(ts[SDE_ROTATOR_TS_RETIRE],
ts[SDE_ROTATOR_TS_DONE])),
ktime_to_us(ktime_sub(ts[SDE_ROTATOR_TS_SRCDQB],
ts[SDE_ROTATOR_TS_RETIRE])),
ktime_to_us(ktime_sub(ts[SDE_ROTATOR_TS_DSTDQB],
ts[SDE_ROTATOR_TS_RETIRE])),
proc_time, sw_overhead_time);
proc_max = max(proc_max, proc_time);
proc_min = min(proc_min, proc_time);
proc_avg += proc_time;
swoh_max = max(swoh_max, sw_overhead_time);
swoh_min = min(swoh_min, sw_overhead_time);
swoh_avg += sw_overhead_time;
}
proc_avg = (num_events) ?
DIV_ROUND_CLOSEST_ULL(proc_avg, num_events) : 0;
swoh_avg = (num_events) ?
DIV_ROUND_CLOSEST_ULL(swoh_avg, num_events) : 0;
seq_printf(s, "count:%llu\n", count);
seq_printf(s, "fai1:%llu\n", stats->fail_count);
seq_printf(s, "t_max:%lld\n", proc_max);
seq_printf(s, "t_min:%lld\n", proc_min);
seq_printf(s, "t_avg:%lld\n", proc_avg);
seq_printf(s, "swoh_max:%lld\n", swoh_max);
seq_printf(s, "swoh_min:%lld\n", swoh_min);
seq_printf(s, "swoh_avg:%lld\n", swoh_avg);
return 0;
}
/*
* sde_rotator_stat_write - Clear statistics on write to this debugfs file.
* @t_file:
* @t_char:
* @t_size_t:
* @t_lof_t:
*/
static ssize_t sde_rotator_stat_write(struct file *t_file,
const char *t_char, size_t t_size_t, loff_t *t_loff_t)
{
struct seq_file *s = t_file->private_data;
struct sde_rotator_device *rot_dev = s->private;
struct sde_rotator_statistics *stats = &rot_dev->stats;
char buf[128];
mutex_lock(&rot_dev->lock);
sde_rot_mgr_lock(rot_dev->mgr);
memset(stats, 0, sizeof(struct sde_rotator_statistics));
sde_rot_mgr_unlock(rot_dev->mgr);
mutex_unlock(&rot_dev->lock);
return simple_write_to_buffer(buf, sizeof(buf),
t_loff_t, t_char, t_size_t);
}
/*
* sde_rotator_raw_show - Show raw statistics on read from this debugfs file
* @s: Pointer to sequence file structure
* @data: Pointer to private data structure
*/
static int sde_rotator_raw_show(struct seq_file *s, void *data)
{
int i, j, offset;
struct sde_rotator_device *rot_dev = s->private;
struct sde_rotator_statistics *stats = &rot_dev->stats;
u64 count = stats->count;
int num_events;
if (count > SDE_ROTATOR_NUM_EVENTS) {
num_events = SDE_ROTATOR_NUM_EVENTS;
offset = count % SDE_ROTATOR_NUM_EVENTS;
} else {
num_events = count;
offset = 0;
}
for (i = 0; i < num_events; i++) {
int k = (offset + i) % SDE_ROTATOR_NUM_EVENTS;
ktime_t *ts = stats->ts[k];
seq_printf(s, "%d ", i);
for (j = 0; j < SDE_ROTATOR_NUM_TIMESTAMPS; j++)
seq_printf(s, "%lld ", ktime_to_us(ts[j]));
seq_puts(s, "\n");
}
return 0;
}
/*
* sde_rotator_dbg_open - Processed statistics debugfs file open function
* @inode:
* @file:
*/
static int sde_rotator_stat_open(struct inode *inode, struct file *file)
{
return single_open(file, sde_rotator_stat_show, inode->i_private);
}
/*
* sde_rotator_dbg_open - Raw statistics debugfs file open function
* @inode:
* @file:
*/
static int sde_rotator_raw_open(struct inode *inode, struct file *file)
{
return single_open(file, sde_rotator_raw_show, inode->i_private);
}
/*
* sde_rotator_dbg_open - Raw statistics debugfs file open function
* @mdata: Pointer to rotator global data
* @debugfs_root: Pointer to parent debugfs node
*/
static int sde_rotator_base_create_debugfs(
struct sde_rot_data_type *mdata,
struct dentry *debugfs_root)
{
if (!debugfs_create_u32("iommu_ref_cnt", 0444,
debugfs_root, &mdata->iommu_ref_cnt)) {
SDEROT_WARN("failed to create debugfs iommu ref cnt\n");
return -EINVAL;
}
mdata->clk_always_on = false;
if (!debugfs_create_bool("clk_always_on", 0644,
debugfs_root, &mdata->clk_always_on)) {
SDEROT_WARN("failed to create debugfs clk_always_on\n");
return -EINVAL;
}
return 0;
}
/*
* sde_rotator_dbg_open - Raw statistics debugfs file open function
* @mgr: Pointer to rotator manager structure
* @debugfs_root: Pointer to parent debugfs node
*/
static int sde_rotator_core_create_debugfs(
struct sde_rot_mgr *mgr,
struct dentry *debugfs_root)
{
int ret;
if (!debugfs_create_u32("hwacquire_timeout", 0644,
debugfs_root, &mgr->hwacquire_timeout)) {
SDEROT_WARN("failed to create debugfs hw acquire timeout\n");
return -EINVAL;
}
if (!debugfs_create_u32("ppc_numer", 0644,
debugfs_root, &mgr->pixel_per_clk.numer)) {
SDEROT_WARN("failed to create debugfs ppc numerator\n");
return -EINVAL;
}
if (!debugfs_create_u32("ppc_denom", 0644,
debugfs_root, &mgr->pixel_per_clk.denom)) {
SDEROT_WARN("failed to create debugfs ppc denominator\n");
return -EINVAL;
}
if (!debugfs_create_u64("enable_bw_vote", 0644,
debugfs_root, &mgr->enable_bw_vote)) {
SDEROT_WARN("failed to create enable_bw_vote\n");
return -EINVAL;
}
if (mgr->ops_hw_create_debugfs) {
ret = mgr->ops_hw_create_debugfs(mgr, debugfs_root);
if (ret)
return ret;
}
return 0;
}
static const struct file_operations sde_rot_evtlog_fops = {
.open = sde_rot_evtlog_dump_open,
.read = sde_rot_evtlog_dump_read,
.write = sde_rot_evtlog_dump_write,
};
static int sde_rotator_evtlog_create_debugfs(
struct sde_rot_mgr *mgr,
struct dentry *debugfs_root)
{
int i;
sde_rot_dbg_evtlog.evtlog = debugfs_create_dir("evtlog", debugfs_root);
if (IS_ERR_OR_NULL(sde_rot_dbg_evtlog.evtlog)) {
pr_err("debugfs_create_dir fail, error %ld\n",
PTR_ERR(sde_rot_dbg_evtlog.evtlog));
sde_rot_dbg_evtlog.evtlog = NULL;
return -ENODEV;
}
INIT_WORK(&sde_rot_dbg_evtlog.evtlog_dump_work,
sde_rot_evtlog_debug_work);
sde_rot_dbg_evtlog.work_panic = false;
for (i = 0; i < SDE_ROT_EVTLOG_ENTRY; i++)
sde_rot_dbg_evtlog.logs[i].counter = i;
debugfs_create_file("dump", 0644, sde_rot_dbg_evtlog.evtlog, NULL,
&sde_rot_evtlog_fops);
debugfs_create_u32("enable", 0644, sde_rot_dbg_evtlog.evtlog,
&sde_rot_dbg_evtlog.evtlog_enable);
debugfs_create_u32("panic", 0644, sde_rot_dbg_evtlog.evtlog,
&sde_rot_dbg_evtlog.panic_on_err);
debugfs_create_u32("reg_dump", 0644, sde_rot_dbg_evtlog.evtlog,
&sde_rot_dbg_evtlog.enable_reg_dump);
debugfs_create_u32("vbif_dbgbus_dump", 0644, sde_rot_dbg_evtlog.evtlog,
&sde_rot_dbg_evtlog.enable_vbif_dbgbus_dump);
debugfs_create_u32("rot_dbgbus_dump", 0644, sde_rot_dbg_evtlog.evtlog,
&sde_rot_dbg_evtlog.enable_rot_dbgbus_dump);
sde_rot_dbg_evtlog.evtlog_enable = SDE_EVTLOG_DEFAULT_ENABLE;
sde_rot_dbg_evtlog.panic_on_err = SDE_EVTLOG_DEFAULT_PANIC;
sde_rot_dbg_evtlog.enable_reg_dump = SDE_EVTLOG_DEFAULT_REGDUMP;
sde_rot_dbg_evtlog.enable_vbif_dbgbus_dump =
SDE_EVTLOG_DEFAULT_VBIF_DBGBUSDUMP;
sde_rot_dbg_evtlog.enable_rot_dbgbus_dump =
SDE_EVTLOG_DEFAULT_ROT_DBGBUSDUMP;
pr_info("evtlog_status: enable:%d, panic:%d, dump:%d\n",
sde_rot_dbg_evtlog.evtlog_enable,
sde_rot_dbg_evtlog.panic_on_err,
sde_rot_dbg_evtlog.enable_reg_dump);
return 0;
}
static int sde_rotator_perf_create_debugfs(
struct sde_rotator_device *rot_dev,
struct dentry *debugfs_root)
{
rot_dev->perf_root = debugfs_create_dir("perf", debugfs_root);
if (IS_ERR_OR_NULL(rot_dev->perf_root)) {
pr_err("debugfs_create_dir for perf failed, error %ld\n",
PTR_ERR(rot_dev->perf_root));
rot_dev->perf_root = NULL;
return -ENODEV;
}
rot_dev->min_rot_clk = 0;
debugfs_create_u32("min_rot_clk", 0644,
rot_dev->perf_root, &rot_dev->min_rot_clk);
rot_dev->min_bw = 0;
debugfs_create_u32("min_bw", 0644,
rot_dev->perf_root, &rot_dev->min_bw);
rot_dev->min_overhead_us = 0;
debugfs_create_u32("min_overhead_us", 0644,
rot_dev->perf_root, &rot_dev->min_overhead_us);
return 0;
}
/*
* struct sde_rotator_stat_ops - processed statistics file operations
*/
static const struct file_operations sde_rotator_stat_ops = {
.open = sde_rotator_stat_open,
.read = seq_read,
.write = sde_rotator_stat_write,
.llseek = seq_lseek,
.release = single_release
};
/*
* struct sde_rotator_raw_ops - raw statistics file operations
*/
static const struct file_operations sde_rotator_raw_ops = {
.open = sde_rotator_raw_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release
};
static int sde_rotator_debug_base_open(struct inode *inode, struct file *file)
{
/* non-seekable */
file->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE);
file->private_data = inode->i_private;
return 0;
}
static int sde_rotator_debug_base_release(struct inode *inode,
struct file *file)
{
struct sde_rotator_debug_base *dbg = file->private_data;
if (dbg) {
mutex_lock(&dbg->buflock);
kfree(dbg->buf);
dbg->buf_len = 0;
dbg->buf = NULL;
mutex_unlock(&dbg->buflock);
}
return 0;
}
static ssize_t sde_rotator_debug_base_offset_write(struct file *file,
const char __user *user_buf, size_t count, loff_t *ppos)
{
struct sde_rotator_debug_base *dbg = file->private_data;
u32 off = 0;
u32 cnt = SDE_ROT_DEFAULT_BASE_REG_CNT;
char buf[24];
if (!dbg)
return -ENODEV;
if (count >= sizeof(buf))
return -EFAULT;
if (copy_from_user(buf, user_buf, count))
return -EFAULT;
buf[count] = 0;
if (sscanf(buf, "%5x %x", &off, &cnt) < 2)
return -EINVAL;
if (off % sizeof(u32))
return -EINVAL;
if (off > dbg->max_offset)
return -EINVAL;
if (cnt > (dbg->max_offset - off))
cnt = dbg->max_offset - off;
mutex_lock(&dbg->buflock);
dbg->off = off;
dbg->cnt = cnt;
mutex_unlock(&dbg->buflock);
SDEROT_DBG("offset=%x cnt=%x\n", off, cnt);
return count;
}
static ssize_t sde_rotator_debug_base_offset_read(struct file *file,
char __user *buff, size_t count, loff_t *ppos)
{
struct sde_rotator_debug_base *dbg = file->private_data;
int len = 0;
char buf[24] = {'\0'};
if (!dbg)
return -ENODEV;
if (*ppos)
return 0; /* the end */
mutex_lock(&dbg->buflock);
len = snprintf(buf, sizeof(buf), "0x%08zx %zx\n", dbg->off, dbg->cnt);
mutex_unlock(&dbg->buflock);
if (len < 0 || len >= sizeof(buf))
return 0;
if ((count < sizeof(buf)) || copy_to_user(buff, buf, len))
return -EFAULT;
*ppos += len; /* increase offset */
return len;
}
static ssize_t sde_rotator_debug_base_reg_write(struct file *file,
const char __user *user_buf, size_t count, loff_t *ppos)
{
struct sde_rotator_debug_base *dbg = file->private_data;
size_t off;
u32 data, cnt;
char buf[24];
if (!dbg)
return -ENODEV;
if (count >= sizeof(buf))
return -EFAULT;
if (copy_from_user(buf, user_buf, count))
return -EFAULT;
buf[count] = 0;
cnt = sscanf(buf, "%zx %x", &off, &data);
if (cnt < 2)
return -EFAULT;
if (off % sizeof(u32))
return -EFAULT;
if (off >= dbg->max_offset)
return -EFAULT;
mutex_lock(&dbg->buflock);
/* Enable Clock for register access */
sde_rotator_clk_ctrl(dbg->mgr, true);
writel_relaxed(data, dbg->base + off);
/* Disable Clock after register access */
sde_rotator_clk_ctrl(dbg->mgr, false);
mutex_unlock(&dbg->buflock);
SDEROT_DBG("addr=%zx data=%x\n", off, data);
return count;
}
static ssize_t sde_rotator_debug_base_reg_read(struct file *file,
char __user *user_buf, size_t count, loff_t *ppos)
{
struct sde_rotator_debug_base *dbg = file->private_data;
size_t len;
int rc = 0;
if (!dbg) {
SDEROT_ERR("invalid handle\n");
return -ENODEV;
}
mutex_lock(&dbg->buflock);
if (!dbg->buf) {
char dump_buf[64];
char *ptr;
int cnt, tot;
dbg->buf_len = sizeof(dump_buf) *
DIV_ROUND_UP(dbg->cnt, ROW_BYTES);
dbg->buf = kzalloc(dbg->buf_len, GFP_KERNEL);
if (!dbg->buf) {
SDEROT_ERR("not enough memory to hold reg dump\n");
rc = -ENOMEM;
goto debug_read_error;
}
if (dbg->off % sizeof(u32))
return -EFAULT;
ptr = dbg->base + dbg->off;
tot = 0;
/* Enable clock for register access */
sde_rotator_clk_ctrl(dbg->mgr, true);
for (cnt = dbg->cnt; cnt > 0; cnt -= ROW_BYTES) {
hex_dump_to_buffer(ptr, min(cnt, ROW_BYTES),
ROW_BYTES, GROUP_BYTES, dump_buf,
sizeof(dump_buf), false);
len = scnprintf(dbg->buf + tot, dbg->buf_len - tot,
"0x%08x: %s\n",
((int) (unsigned long) ptr) -
((int) (unsigned long) dbg->base),
dump_buf);
ptr += ROW_BYTES;
tot += len;
if (tot >= dbg->buf_len)
break;
}
/* Disable clock after register access */
sde_rotator_clk_ctrl(dbg->mgr, false);
dbg->buf_len = tot;
}
if (*ppos >= dbg->buf_len) {
rc = 0; /* done reading */
goto debug_read_error;
}
len = min(count, dbg->buf_len - (size_t) *ppos);
if (copy_to_user(user_buf, dbg->buf + *ppos, len)) {
SDEROT_ERR("failed to copy to user\n");
rc = -EFAULT;
goto debug_read_error;
}
*ppos += len; /* increase offset */
mutex_unlock(&dbg->buflock);
return len;
debug_read_error:
mutex_unlock(&dbg->buflock);
return rc;
}
static const struct file_operations sde_rotator_off_fops = {
.open = sde_rotator_debug_base_open,
.release = sde_rotator_debug_base_release,
.read = sde_rotator_debug_base_offset_read,
.write = sde_rotator_debug_base_offset_write,
};
static const struct file_operations sde_rotator_reg_fops = {
.open = sde_rotator_debug_base_open,
.release = sde_rotator_debug_base_release,
.read = sde_rotator_debug_base_reg_read,
.write = sde_rotator_debug_base_reg_write,
};
int sde_rotator_debug_register_base(struct sde_rotator_device *rot_dev,
struct dentry *debugfs_root,
const char *name,
struct sde_io_data *io_data)
{
struct sde_rotator_debug_base *dbg;
struct dentry *ent_off, *ent_reg;
char dbgname[80] = "";
int prefix_len = 0;
if (!io_data)
return -EINVAL;
dbg = kzalloc(sizeof(*dbg), GFP_KERNEL);
if (!dbg)
return -ENOMEM;
mutex_init(&dbg->buflock);
mutex_lock(&dbg->buflock);
if (name)
strlcpy(dbg->name, name, sizeof(dbg->name));
dbg->base = io_data->base;
dbg->max_offset = io_data->len;
dbg->off = 0;
dbg->cnt = SDE_ROT_DEFAULT_BASE_REG_CNT;
if (name) {
if (strcmp(name, "sde"))
prefix_len = snprintf(dbgname, sizeof(dbgname), "%s_",
name);
else
/*
* For SDE Rotator registers block, the IO base address
* is based on MDP IO address base. It is necessary to
* apply the initial offset to it from the first
* regdump setting.
*/
dbg->base += rot_dev->mdata->regdump ?
rot_dev->mdata->regdump[0].offset : 0;
}
mutex_unlock(&dbg->buflock);
strlcpy(dbgname + prefix_len, "off", sizeof(dbgname) - prefix_len);
ent_off = debugfs_create_file(dbgname, 0644, debugfs_root, dbg,
&sde_rotator_off_fops);
if (IS_ERR_OR_NULL(ent_off)) {
SDEROT_ERR("debugfs_create_file: offset fail\n");
goto off_fail;
}
strlcpy(dbgname + prefix_len, "reg", sizeof(dbgname) - prefix_len);
ent_reg = debugfs_create_file(dbgname, 0644, debugfs_root, dbg,
&sde_rotator_reg_fops);
if (IS_ERR_OR_NULL(ent_reg)) {
SDEROT_ERR("debugfs_create_file: reg fail\n");
goto reg_fail;
}
mutex_lock(&dbg->buflock);
dbg->mgr = rot_dev->mgr;
mutex_unlock(&dbg->buflock);
return 0;
reg_fail:
debugfs_remove(ent_off);
off_fail:
kfree(dbg);
return -ENODEV;
}
/*
* sde_rotator_create_debugfs - Setup rotator debugfs directory structure.
* @rot_dev: Pointer to rotator device
*/
struct dentry *sde_rotator_create_debugfs(
struct sde_rotator_device *rot_dev)
{
struct dentry *debugfs_root;
char dirname[32] = {0};
snprintf(dirname, sizeof(dirname), "%s%d",
SDE_ROTATOR_DRV_NAME, rot_dev->dev->id);
debugfs_root = debugfs_create_dir(dirname, NULL);
if (!debugfs_root) {
SDEROT_ERR("fail create debugfs root\n");
return NULL;
}
if (!debugfs_create_file("stats", 0644,
debugfs_root, rot_dev, &sde_rotator_stat_ops)) {
SDEROT_ERR("fail create debugfs stats\n");
debugfs_remove_recursive(debugfs_root);
return NULL;
}
if (!debugfs_create_file("raw", 0644,
debugfs_root, rot_dev, &sde_rotator_raw_ops)) {
SDEROT_ERR("fail create debugfs raw\n");
debugfs_remove_recursive(debugfs_root);
return NULL;
}
if (!debugfs_create_u32("fence_timeout", 0644,
debugfs_root, &rot_dev->fence_timeout)) {
SDEROT_ERR("fail create fence_timeout\n");
debugfs_remove_recursive(debugfs_root);
return NULL;
}
if (!debugfs_create_u32("open_timeout", 0644,
debugfs_root, &rot_dev->open_timeout)) {
SDEROT_ERR("fail create open_timeout\n");
debugfs_remove_recursive(debugfs_root);
return NULL;
}
if (!debugfs_create_u32("disable_syscache", 0644,
debugfs_root, &rot_dev->disable_syscache)) {
SDEROT_ERR("fail create disable_syscache\n");
debugfs_remove_recursive(debugfs_root);
return NULL;
}
if (!debugfs_create_u32("streamoff_timeout", 0644,
debugfs_root, &rot_dev->streamoff_timeout)) {
SDEROT_ERR("fail create streamoff_timeout\n");
debugfs_remove_recursive(debugfs_root);
return NULL;
}
if (!debugfs_create_u32("early_submit", 0644,
debugfs_root, &rot_dev->early_submit)) {
SDEROT_ERR("fail create early_submit\n");
debugfs_remove_recursive(debugfs_root);
return NULL;
}
if (sde_rotator_base_create_debugfs(rot_dev->mdata, debugfs_root)) {
SDEROT_ERR("fail create base debugfs\n");
debugfs_remove_recursive(debugfs_root);
return NULL;
}
if (sde_rotator_core_create_debugfs(rot_dev->mgr, debugfs_root)) {
SDEROT_ERR("fail create core debugfs\n");
debugfs_remove_recursive(debugfs_root);
return NULL;
}
if (sde_rotator_evtlog_create_debugfs(rot_dev->mgr, debugfs_root)) {
SDEROT_ERR("fail create evtlog debugfs\n");
debugfs_remove_recursive(debugfs_root);
return NULL;
}
if (sde_rotator_perf_create_debugfs(rot_dev, debugfs_root)) {
SDEROT_ERR("fail create perf debugfs\n");
debugfs_remove_recursive(debugfs_root);
return NULL;
}
if (sde_rotator_debug_register_base(rot_dev, debugfs_root,
"sde", &rot_dev->mdata->sde_io)) {
SDEROT_ERR("fail create debug register for sde rotator\n");
debugfs_remove_recursive(debugfs_root);
return NULL;
}
if (sde_rotator_debug_register_base(rot_dev, debugfs_root,
"vbif_nrt", &rot_dev->mdata->vbif_nrt_io)) {
SDEROT_ERR("fail create debug register for sderot vbif_nrt\n");
debugfs_remove_recursive(debugfs_root);
return NULL;
}
return debugfs_root;
}
/*
* sde_rotator_destroy_debugfs - Destroy rotator debugfs directory structure.
* @rot_dev: Pointer to rotator debugfs
*/
void sde_rotator_destroy_debugfs(struct dentry *debugfs)
{
debugfs_remove_recursive(debugfs);
}