/* Copyright (c) 2009-2013, 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 #include #include #include #include #include #include #include "mdss.h" #include "mdss_mdp.h" #include "mdss_debug.h" #define DEFAULT_BASE_REG_CNT 0x100 #define GROUP_BYTES 4 #define ROW_BYTES 16 struct mdss_debug_data { struct dentry *root; struct list_head base_list; }; struct mdss_debug_base { struct mdss_debug_data *mdd; void __iomem *base; size_t off; size_t cnt; size_t max_offset; char *buf; size_t buf_len; struct list_head head; }; static int mdss_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 mdss_debug_base_release(struct inode *inode, struct file *file) { struct mdss_debug_base *dbg = file->private_data; if (dbg && dbg->buf) { kfree(dbg->buf); dbg->buf_len = 0; dbg->buf = NULL; } return 0; } static ssize_t mdss_debug_base_offset_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { struct mdss_debug_base *dbg = file->private_data; u32 off = 0; u32 cnt = 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; /* end of string */ sscanf(buf, "%5x %x", &off, &cnt); if (off > dbg->max_offset) return -EINVAL; if (cnt > (dbg->max_offset - off)) cnt = dbg->max_offset - off; dbg->off = off; dbg->cnt = cnt; pr_debug("offset=%x cnt=%x\n", off, cnt); return count; } static ssize_t mdss_debug_base_offset_read(struct file *file, char __user *buff, size_t count, loff_t *ppos) { struct mdss_debug_base *dbg = file->private_data; int len = 0; char buf[24]; if (!dbg) return -ENODEV; if (*ppos) return 0; /* the end */ len = snprintf(buf, sizeof(buf), "0x%08x %x\n", dbg->off, dbg->cnt); if (len < 0) return 0; if (copy_to_user(buff, buf, len)) return -EFAULT; *ppos += len; /* increase offset */ return len; } static ssize_t mdss_debug_base_reg_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { struct mdss_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; /* end of string */ cnt = sscanf(buf, "%x %x", &off, &data); if (cnt < 2) return -EFAULT; if (off >= dbg->max_offset) return -EFAULT; mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false); writel_relaxed(data, dbg->base + off); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false); pr_debug("addr=%x data=%x\n", off, data); return count; } static ssize_t mdss_debug_base_reg_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { struct mdss_debug_base *dbg = file->private_data; size_t len; if (!dbg) { pr_err("invalid handle\n"); return -ENODEV; } 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) { pr_err("not enough memory to hold reg dump\n"); return -ENOMEM; } ptr = dbg->base + dbg->off; tot = 0; mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false); 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)ptr) - ((int)dbg->base), dump_buf); ptr += ROW_BYTES; tot += len; if (tot >= dbg->buf_len) break; } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false); dbg->buf_len = tot; } if (*ppos >= dbg->buf_len) return 0; /* done reading */ len = min(count, dbg->buf_len - (size_t) *ppos); if (copy_to_user(user_buf, dbg->buf + *ppos, len)) { pr_err("failed to copy to user\n"); return -EFAULT; } *ppos += len; /* increase offset */ return len; } static const struct file_operations mdss_off_fops = { .open = mdss_debug_base_open, .release = mdss_debug_base_release, .read = mdss_debug_base_offset_read, .write = mdss_debug_base_offset_write, }; static const struct file_operations mdss_reg_fops = { .open = mdss_debug_base_open, .release = mdss_debug_base_release, .read = mdss_debug_base_reg_read, .write = mdss_debug_base_reg_write, }; int mdss_debug_register_base(const char *name, void __iomem *base, size_t max_offset) { struct mdss_data_type *mdata = mdss_res; struct mdss_debug_data *mdd; struct mdss_debug_base *dbg; struct dentry *ent_off, *ent_reg; char dn[80] = ""; int prefix_len = 0; if (!mdata || !mdata->debug_data) return -ENODEV; mdd = mdata->debug_data; dbg = kzalloc(sizeof(*dbg), GFP_KERNEL); if (!dbg) return -ENOMEM; dbg->base = base; dbg->max_offset = max_offset; dbg->off = 0; dbg->cnt = DEFAULT_BASE_REG_CNT; if (name) prefix_len = snprintf(dn, sizeof(dn), "%s_", name); strlcpy(dn + prefix_len, "off", sizeof(dn) - prefix_len); ent_off = debugfs_create_file(dn, 0644, mdd->root, dbg, &mdss_off_fops); if (IS_ERR_OR_NULL(ent_off)) { pr_err("debugfs_create_file: offset fail\n"); goto off_fail; } strlcpy(dn + prefix_len, "reg", sizeof(dn) - prefix_len); ent_reg = debugfs_create_file(dn, 0644, mdd->root, dbg, &mdss_reg_fops); if (IS_ERR_OR_NULL(ent_reg)) { pr_err("debugfs_create_file: reg fail\n"); goto reg_fail; } list_add(&dbg->head, &mdd->base_list); return 0; reg_fail: debugfs_remove(ent_off); off_fail: kfree(dbg); return -ENODEV; } static int mdss_debug_stat_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 mdss_debug_stat_release(struct inode *inode, struct file *file) { return 0; } static int mdss_debug_stat_ctl_dump(struct mdss_mdp_ctl *ctl, char *bp, int len) { int tot = 0; if (!ctl->ref_cnt) return 0; if (ctl->intf_num) { tot = scnprintf(bp, len, "intf%d: play: %08u \tvsync: %08u \tunderrun: %08u\n", ctl->intf_num, ctl->play_cnt, ctl->vsync_cnt, ctl->underrun_cnt); } else { tot = scnprintf(bp, len, "wb: \tmode=%x \tplay: %08u\n", ctl->opmode, ctl->play_cnt); } return tot; } static ssize_t mdss_debug_stat_read(struct file *file, char __user *buff, size_t count, loff_t *ppos) { struct mdss_data_type *mdata = file->private_data; struct mdss_mdp_pipe *pipe; int i, len, tot; char bp[512]; if (*ppos) return 0; /* the end */ len = sizeof(bp); tot = scnprintf(bp, len, "\nmdp:\n"); for (i = 0; i < mdata->nctl; i++) tot += mdss_debug_stat_ctl_dump(mdata->ctl_off + i, bp + tot, len - tot); tot += scnprintf(bp + tot, len - tot, "\n"); for (i = 0; i < mdata->nvig_pipes; i++) { pipe = mdata->vig_pipes + i; tot += scnprintf(bp + tot, len - tot, "VIG%d : %08u\t", i, pipe->play_cnt); } tot += scnprintf(bp + tot, len - tot, "\n"); for (i = 0; i < mdata->nrgb_pipes; i++) { pipe = mdata->rgb_pipes + i; tot += scnprintf(bp + tot, len - tot, "RGB%d : %08u\t", i, pipe->play_cnt); } tot += scnprintf(bp + tot, len - tot, "\n"); for (i = 0; i < mdata->ndma_pipes; i++) { pipe = mdata->dma_pipes + i; tot += scnprintf(bp + tot, len - tot, "DMA%d : %08u\t", i, pipe->play_cnt); } tot += scnprintf(bp + tot, len - tot, "\n"); if (copy_to_user(buff, bp, tot)) return -EFAULT; *ppos += tot; /* increase offset */ return tot; } static const struct file_operations mdss_stat_fops = { .open = mdss_debug_stat_open, .release = mdss_debug_stat_release, .read = mdss_debug_stat_read, }; static int mdss_debugfs_cleanup(struct mdss_debug_data *mdd) { struct mdss_debug_base *base, *tmp; if (!mdd) return 0; list_for_each_entry_safe(base, tmp, &mdd->base_list, head) { list_del(&base->head); kfree(base); } if (mdd->root) debugfs_remove_recursive(mdd->root); kfree(mdd); return 0; } int mdss_debugfs_init(struct mdss_data_type *mdata) { struct mdss_debug_data *mdd; if (mdata->debug_data) { pr_warn("mdss debugfs already initialized\n"); return -EBUSY; } mdd = kzalloc(sizeof(*mdd), GFP_KERNEL); if (!mdd) { pr_err("no memory to create mdss debug data\n"); return -ENOMEM; } INIT_LIST_HEAD(&mdd->base_list); mdd->root = debugfs_create_dir("mdp", NULL); if (IS_ERR_OR_NULL(mdd->root)) { pr_err("debugfs_create_dir fail, error %ld\n", PTR_ERR(mdd->root)); mdd->root = NULL; mdss_debugfs_cleanup(mdd); return -ENODEV; } debugfs_create_file("stat", 0644, mdd->root, mdata, &mdss_stat_fops); debugfs_create_u32("min_mdp_clk", 0644, mdd->root, (u32 *)&mdata->min_mdp_clk); mdata->debug_data = mdd; return 0; } int mdss_debugfs_remove(struct mdss_data_type *mdata) { struct mdss_debug_data *mdd = mdata->debug_data; mdss_debugfs_cleanup(mdd); return 0; } static struct mdss_mdp_misr_map { u32 ctrl_reg; u32 value_reg; u32 crc_op_mode; u32 crc_index; u32 crc_value[MISR_CRC_BATCH_SIZE]; } mdss_mdp_misr_table[DISPLAY_MISR_MAX] = { [DISPLAY_MISR_DSI0] = { .ctrl_reg = MDSS_MDP_LP_MISR_CTRL_DSI0, .value_reg = MDSS_MDP_LP_MISR_SIGN_DSI0, }, [DISPLAY_MISR_DSI1] = { .ctrl_reg = MDSS_MDP_LP_MISR_CTRL_DSI1, .value_reg = MDSS_MDP_LP_MISR_SIGN_DSI1, }, [DISPLAY_MISR_EDP] = { .ctrl_reg = MDSS_MDP_LP_MISR_CTRL_EDP, .value_reg = MDSS_MDP_LP_MISR_SIGN_EDP, }, [DISPLAY_MISR_HDMI] = { .ctrl_reg = MDSS_MDP_LP_MISR_CTRL_HDMI, .value_reg = MDSS_MDP_LP_MISR_SIGN_HDMI, }, }; static inline struct mdss_mdp_misr_map *mdss_misr_get_map(u32 block_id) { struct mdss_mdp_misr_map *map; if (block_id > DISPLAY_MISR_LCDC) { pr_err("MISR Block id (%d) out of range\n", block_id); return NULL; } map = mdss_mdp_misr_table + block_id; if ((map->ctrl_reg == 0) || (map->value_reg == 0)) { pr_err("MISR Block id (%d) config not found\n", block_id); return NULL; } return map; } int mdss_misr_crc_set(struct mdss_data_type *mdata, struct mdp_misr *req) { struct mdss_mdp_misr_map *map; u32 config = 0; map = mdss_misr_get_map(req->block_id); if (!map) { pr_err("Invalid MISR Block=%d\n", req->block_id); return -EINVAL; } map->crc_op_mode = req->crc_op_mode; memset(map->crc_value, 0, sizeof(map->crc_value)); pr_debug("MISR Config (BlockId %d) (Frame Count = %d)\n", req->block_id, req->frame_count); config = (MDSS_MDP_LP_MISR_CTRL_FRAME_COUNT_MASK & req->frame_count) | (MDSS_MDP_LP_MISR_CTRL_ENABLE); writel_relaxed(MDSS_MDP_LP_MISR_CTRL_STATUS_CLEAR, mdata->mdp_base + map->ctrl_reg); /* ensure clear is done */ wmb(); if (MISR_OP_BM == map->crc_op_mode) { writel_relaxed(MISR_CRC_BATCH_CFG, mdata->mdp_base + map->ctrl_reg); } else { writel_relaxed(config, mdata->mdp_base + map->ctrl_reg); config = readl_relaxed(mdata->mdp_base + map->ctrl_reg); pr_debug("MISR_CTRL = 0x%x", config); } return 0; } int mdss_misr_crc_get(struct mdss_data_type *mdata, struct mdp_misr *resp) { struct mdss_mdp_misr_map *map; u32 status; int ret = 0; int i; map = mdss_misr_get_map(resp->block_id); if (!map) { pr_err("Invalid MISR Block=%d\n", resp->block_id); return -EINVAL; } switch (map->crc_op_mode) { case MISR_OP_SFM: case MISR_OP_MFM: ret = readl_poll_timeout(mdata->mdp_base + map->ctrl_reg, status, status & MDSS_MDP_LP_MISR_CTRL_STATUS, MISR_POLL_SLEEP, MISR_POLL_TIMEOUT); pr_debug("Status of Get MISR_CTRL = 0x%x", status); if (ret == 0) { resp->crc_value[0] = readl_relaxed(mdata->mdp_base + map->value_reg); pr_debug("CRC %d=0x%x\n", resp->block_id, resp->crc_value[0]); } else { pr_warn("MISR %d busy with status 0x%x\n", resp->block_id, status); } break; case MISR_OP_BM: for (i = 0; i < MISR_CRC_BATCH_SIZE; i++) resp->crc_value[i] = map->crc_value[i]; map->crc_index = 0; break; default: ret = -ENOSYS; break; } return ret; } /* This function is expected to be called from interrupt context */ void mdss_misr_crc_collect(struct mdss_data_type *mdata, int block_id) { struct mdss_mdp_misr_map *map; u32 status, config; map = mdss_misr_get_map(block_id); if (!map || (map->crc_op_mode != MISR_OP_BM)) return; config = MISR_CRC_BATCH_CFG; status = readl_relaxed(mdata->mdp_base + map->ctrl_reg); if (status & MDSS_MDP_LP_MISR_CTRL_STATUS) { map->crc_value[map->crc_index] = readl_relaxed(mdata->mdp_base + map->value_reg); map->crc_index++; if (map->crc_index == MISR_CRC_BATCH_SIZE) map->crc_index = 0; config |= MDSS_MDP_LP_MISR_CTRL_STATUS_CLEAR; } writel_relaxed(config, mdata->mdp_base + map->ctrl_reg); }