590 lines
13 KiB
C
590 lines
13 KiB
C
/* 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 <linux/debugfs.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/list.h>
|
|
#include <linux/printk.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
|
|
#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);
|
|
}
|