/* Copyright (c) 2009-2012, 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 "mdss.h" #include "mdss_mdp.h" #include "mdss_debug.h" #define DEFAULT_BASE_REG_CNT 128 #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, 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 %d", &off, &cnt); if (off > dbg->max_offset) return -EINVAL; if (cnt <= 0) cnt = DEFAULT_BASE_REG_CNT; 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 %d\n", dbg->off, dbg->off); 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_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; } 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; }