| /* 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 = ®dump[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); |
| } |