blob: aeb3f866daf3b376879df64736394cee5886899b [file] [log] [blame]
/* 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_mdp_hwio.h"
#include "mdss_debug.h"
#define DEFAULT_BASE_REG_CNT 0x100
#define GROUP_BYTES 4
#define ROW_BYTES 16
#define MAX_VSYNC_COUNT 0xFFFFFFF
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;
struct mdss_data_type *mdata = mdss_res;
size_t off;
u32 data, cnt;
char buf[24];
if (!dbg || !mdata)
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;
if (mdata->debug_inf.debug_enable_clock)
mdata->debug_inf.debug_enable_clock(1);
writel_relaxed(data, dbg->base + off);
if (mdata->debug_inf.debug_enable_clock)
mdata->debug_inf.debug_enable_clock(0);
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;
struct mdss_data_type *mdata = mdss_res;
size_t len;
if (!dbg || !mdata) {
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;
if (mdata->debug_inf.debug_enable_clock)
mdata->debug_inf.debug_enable_clock(1);
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;
}
if (mdata->debug_inf.debug_enable_clock)
mdata->debug_inf.debug_enable_clock(0);
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_inf.debug_data)
return -ENODEV;
mdd = mdata->debug_inf.debug_data;
dbg = kzalloc(sizeof(*dbg), GFP_KERNEL);
if (!dbg)
return -ENOMEM;
if (name)
strlcpy(dbg->name, name, sizeof(dbg->name));
dbg->base = base;
dbg->max_offset = max_offset;
dbg->off = 0;
dbg->cnt = DEFAULT_BASE_REG_CNT;
if (name && strcmp(name, "mdp"))
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 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;
int len, tot;
char bp[512];
if (*ppos)
return 0; /* the end */
len = sizeof(bp);
tot = scnprintf(bp, len, "\nmdp:\n");
if (mdata->debug_inf.debug_dump_stats)
tot += mdata->debug_inf.debug_dump_stats(mdata,
bp + tot, len - tot);
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_inf.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);
if (mdss_create_xlog_debug(mdd)) {
mdss_debugfs_cleanup(mdd);
return -ENODEV;
}
mdata->debug_inf.debug_data = mdd;
return 0;
}
int mdss_debugfs_remove(struct mdss_data_type *mdata)
{
struct mdss_debug_data *mdd = mdata->debug_inf.debug_data;
mdss_debugfs_cleanup(mdd);
mdata->debug_inf.debug_data = NULL;
return 0;
}
void mdss_dump_reg(char __iomem *base, int len)
{
char *addr;
u32 x0, x4, x8, xc;
int i;
addr = base;
if (len % 16)
len += 16;
len /= 16;
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
for (i = 0; i < len; i++) {
x0 = readl_relaxed(addr+0x0);
x4 = readl_relaxed(addr+0x4);
x8 = readl_relaxed(addr+0x8);
xc = readl_relaxed(addr+0xc);
pr_info("%p : %08x %08x %08x %08x\n", addr, x0, x4, x8, xc);
addr += 16;
}
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
}
int vsync_count;
static struct mdss_mdp_misr_map {
u32 ctrl_reg;
u32 value_reg;
u32 crc_op_mode;
u32 crc_index;
bool use_ping;
bool is_ping_full;
bool is_pong_full;
struct mutex crc_lock;
u32 crc_ping[MISR_CRC_BATCH_SIZE];
u32 crc_pong[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,
.crc_op_mode = 0,
.crc_index = 0,
.use_ping = true,
.is_ping_full = false,
.is_pong_full = false,
},
[DISPLAY_MISR_DSI1] = {
.ctrl_reg = MDSS_MDP_LP_MISR_CTRL_DSI1,
.value_reg = MDSS_MDP_LP_MISR_SIGN_DSI1,
.crc_op_mode = 0,
.crc_index = 0,
.use_ping = true,
.is_ping_full = false,
.is_pong_full = false,
},
[DISPLAY_MISR_EDP] = {
.ctrl_reg = MDSS_MDP_LP_MISR_CTRL_EDP,
.value_reg = MDSS_MDP_LP_MISR_SIGN_EDP,
.crc_op_mode = 0,
.crc_index = 0,
.use_ping = true,
.is_ping_full = false,
.is_pong_full = false,
},
[DISPLAY_MISR_HDMI] = {
.ctrl_reg = MDSS_MDP_LP_MISR_CTRL_HDMI,
.value_reg = MDSS_MDP_LP_MISR_SIGN_HDMI,
.crc_op_mode = 0,
.crc_index = 0,
.use_ping = true,
.is_ping_full = false,
.is_pong_full = false,
},
[DISPLAY_MISR_MDP] = {
.ctrl_reg = MDSS_MDP_LP_MISR_CTRL_MDP,
.value_reg = MDSS_MDP_LP_MISR_SIGN_MDP,
.crc_op_mode = 0,
.crc_index = 0,
.use_ping = true,
.is_ping_full = false,
.is_pong_full = false,
},
};
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_MDP) {
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_set(struct mdss_data_type *mdata,
struct mdp_misr *req,
struct mdss_mdp_ctl *ctl)
{
struct mdss_mdp_misr_map *map;
struct mdss_mdp_mixer *mixer;
u32 config = 0, val = 0;
u32 mixer_num = 0;
bool is_valid_wb_mixer = true;
map = mdss_misr_get_map(req->block_id);
if (!map) {
pr_err("Invalid MISR Block=%d\n", req->block_id);
return -EINVAL;
}
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
if (req->block_id == DISPLAY_MISR_MDP) {
mixer = mdss_mdp_mixer_get(ctl, MDSS_MDP_MIXER_MUX_DEFAULT);
mixer_num = mixer->num;
pr_debug("SET MDP MISR BLK to MDSS_MDP_LP_MISR_SEL_LMIX%d_GC\n",
req->block_id);
switch (mixer_num) {
case MDSS_MDP_INTF_LAYERMIXER0:
pr_debug("Use Layer Mixer 0 for WB CRC\n");
val = MDSS_MDP_LP_MISR_SEL_LMIX0_GC;
break;
case MDSS_MDP_INTF_LAYERMIXER1:
pr_debug("Use Layer Mixer 1 for WB CRC\n");
val = MDSS_MDP_LP_MISR_SEL_LMIX1_GC;
break;
case MDSS_MDP_INTF_LAYERMIXER2:
pr_debug("Use Layer Mixer 2 for WB CRC\n");
val = MDSS_MDP_LP_MISR_SEL_LMIX2_GC;
break;
default:
pr_err("Invalid Layer Mixer %d selected for WB CRC\n",
mixer_num);
is_valid_wb_mixer = false;
break;
}
if (is_valid_wb_mixer)
writel_relaxed(val,
mdata->mdp_base + MDSS_MDP_LP_MISR_SEL);
}
vsync_count = 0;
map->crc_op_mode = req->crc_op_mode;
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();
memset(map->crc_ping, 0, sizeof(map->crc_ping));
memset(map->crc_pong, 0, sizeof(map->crc_pong));
map->crc_index = 0;
map->use_ping = true;
map->is_ping_full = false;
map->is_pong_full = false;
if (MISR_OP_BM != map->crc_op_mode) {
writel_relaxed(config,
mdata->mdp_base + map->ctrl_reg);
pr_debug("MISR_CTRL = 0x%x",
readl_relaxed(mdata->mdp_base + map->ctrl_reg));
}
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
return 0;
}
int mdss_misr_get(struct mdss_data_type *mdata,
struct mdp_misr *resp,
struct mdss_mdp_ctl *ctl)
{
struct mdss_mdp_misr_map *map;
u32 status;
int ret = -1;
int i;
map = mdss_misr_get_map(resp->block_id);
if (!map) {
pr_err("Invalid MISR Block=%d\n", resp->block_id);
return -EINVAL;
}
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
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);
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]);
writel_relaxed(0, mdata->mdp_base + map->ctrl_reg);
} else {
mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_START, 1);
ret = readl_poll_timeout(mdata->mdp_base +
map->ctrl_reg, status,
status & MDSS_MDP_LP_MISR_CTRL_STATUS,
MISR_POLL_SLEEP, MISR_POLL_TIMEOUT);
if (ret == 0) {
resp->crc_value[0] =
readl_relaxed(mdata->mdp_base +
map->value_reg);
}
writel_relaxed(0, mdata->mdp_base + map->ctrl_reg);
}
break;
case MISR_OP_BM:
if (map->is_ping_full) {
for (i = 0; i < MISR_CRC_BATCH_SIZE; i++)
resp->crc_value[i] = map->crc_ping[i];
memset(map->crc_ping, 0, sizeof(map->crc_ping));
map->is_ping_full = false;
ret = 0;
} else if (map->is_pong_full) {
for (i = 0; i < MISR_CRC_BATCH_SIZE; i++)
resp->crc_value[i] = map->crc_pong[i];
memset(map->crc_pong, 0, sizeof(map->crc_pong));
map->is_pong_full = false;
ret = 0;
} else {
pr_debug("mdss_mdp_misr_crc_get PING BUF %s\n",
map->is_ping_full ? "FULL" : "EMPTRY");
pr_debug("mdss_mdp_misr_crc_get PONG BUF %s\n",
map->is_pong_full ? "FULL" : "EMPTRY");
}
resp->crc_op_mode = map->crc_op_mode;
break;
default:
ret = -ENOSYS;
break;
}
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
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 = 0;
u32 crc = 0x0BAD0BAD;
bool crc_stored = false;
map = mdss_misr_get_map(block_id);
if (!map || (map->crc_op_mode != MISR_OP_BM))
return;
status = readl_relaxed(mdata->mdp_base + map->ctrl_reg);
if (MDSS_MDP_LP_MISR_CTRL_STATUS & status) {
crc = readl_relaxed(mdata->mdp_base + map->value_reg);
if (map->use_ping) {
if (map->is_ping_full) {
pr_err("PING Buffer FULL\n");
} else {
map->crc_ping[map->crc_index] = crc;
crc_stored = true;
}
} else {
if (map->is_pong_full) {
pr_err("PONG Buffer FULL\n");
} else {
map->crc_pong[map->crc_index] = crc;
crc_stored = true;
}
}
if (crc_stored) {
map->crc_index = (map->crc_index + 1);
if (map->crc_index == MISR_CRC_BATCH_SIZE) {
map->crc_index = 0;
if (true == map->use_ping) {
map->is_ping_full = true;
map->use_ping = false;
} else {
map->is_pong_full = true;
map->use_ping = true;
}
pr_debug("USE BUFF %s\n", map->use_ping ?
"PING" : "PONG");
pr_debug("mdss_misr_crc_collect PING BUF %s\n",
map->is_ping_full ? "FULL" : "EMPTRY");
pr_debug("mdss_misr_crc_collect PONG BUF %s\n",
map->is_pong_full ? "FULL" : "EMPTRY");
}
} else {
pr_err("CRC(%d) Not saved\n", crc);
}
writel_relaxed(MDSS_MDP_LP_MISR_CTRL_STATUS_CLEAR,
mdata->mdp_base + map->ctrl_reg);
writel_relaxed(MISR_CRC_BATCH_CFG,
mdata->mdp_base + map->ctrl_reg);
} else if (0 == status) {
writel_relaxed(MISR_CRC_BATCH_CFG,
mdata->mdp_base + map->ctrl_reg);
pr_debug("$$ Batch CRC Start $$\n");
}
pr_debug("$$ Vsync Count = %d, CRC=0x%x Indx = %d$$\n",
vsync_count, crc, map->crc_index);
if (MAX_VSYNC_COUNT == vsync_count) {
pr_err("RESET vsync_count(%d)\n", vsync_count);
vsync_count = 0;
} else {
vsync_count += 1;
}
}