354 lines
9.7 KiB
C

// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2021 Rockchip Electronics Co., Ltd.
* Author: Sandy Huang <hjc@rock-chips.com>
*/
#include <drm/drm_atomic_uapi.h>
#include <drm/drm_drv.h>
#include <drm/drm_file.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_of.h>
#include <drm/drm_probe_helper.h>
#include <linux/file.h>
#include "rockchip_drm_drv.h"
#include "rockchip_drm_debugfs.h"
#include "rockchip_drm_fb.h"
#if defined(CONFIG_NO_GKI)
#define DUMP_BUF_PATH "/data"
#define to_rockchip_crtc(x) container_of(x, struct rockchip_crtc, crtc)
/**
* struct vop_dump_info - vop dump plane info structure
*
* Store plane info used to write display data to /data/vop_buf/
*
*/
struct vop_dump_info {
/* @win_name human readable vop win name */
const char *win_name;
/* @fb: DRM frame buffer */
struct drm_framebuffer *fb;
/* @src: source coordinates of the plane (in 16.16)*/
struct drm_rect *src;
};
static int temp_pow(int sum, int n)
{
int i;
int temp = sum;
if (n < 1)
return 1;
for (i = 1; i < n ; i++)
sum *= temp;
return sum;
}
static int rockchip_drm_dump_plane_buffer(struct vop_dump_info *dump_info, int frame_count)
{
struct iosys_map map[DRM_FORMAT_MAX_PLANES];
struct iosys_map data[DRM_FORMAT_MAX_PLANES];
struct drm_framebuffer *fb = dump_info->fb;
int ret;
int flags;
const char *ptr;
char file_name[128];
char format_name[5];
void *kvaddr;
struct file *file;
loff_t pos = 0;
struct drm_gem_object *obj = dump_info->fb->obj[0];
struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
snprintf(file_name, sizeof(file_name), "%p4cc", &dump_info->fb->format->format);
strscpy(format_name, file_name, 5);
flags = O_RDWR | O_CREAT;
snprintf(file_name, 100, "%s/%s_fb-%dx%d_stride-%d_offset-%dx%d_act-%dx%d_%s%s_%d.bin",
DUMP_BUF_PATH, dump_info->win_name, dump_info->fb->width, dump_info->fb->height,
dump_info->fb->pitches[0], dump_info->src->x1 >> 16, dump_info->src->y1 >> 16,
drm_rect_width(dump_info->src) >> 16, drm_rect_height(dump_info->src) >> 16,
format_name, rockchip_drm_modifier_to_string(dump_info->fb->modifier), frame_count);
ret = drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE);
if (ret)
return ret;
ret = drm_gem_fb_vmap(fb, map, data);
if (ret) {
DRM_ERROR("Failed to vmap() buffer\n");
goto out_drm_gem_fb_end_cpu_access;
}
kvaddr = data[0].vaddr;
ptr = file_name;
file = filp_open(ptr, flags, 0644);
if (!IS_ERR(file)) {
kernel_write(file, kvaddr, rk_obj->size, &pos);
DRM_INFO("dump file name is:%s\n", file_name);
fput(file);
} else {
DRM_INFO("open %s failed\n", ptr);
}
drm_gem_fb_vunmap(fb, map);
out_drm_gem_fb_end_cpu_access:
drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE);
return 0;
}
int rockchip_drm_crtc_dump_plane_buffer(struct drm_crtc *crtc)
{
struct rockchip_crtc *rockchip_crtc = container_of(crtc, struct rockchip_crtc, crtc);
struct drm_plane *plane;
struct drm_plane_state *pstate;
struct drm_framebuffer *fb;
struct vop_dump_info dump_info;
drm_atomic_crtc_for_each_plane(plane, crtc) {
pstate = plane->state;
fb = pstate->fb;
if (!fb)
continue;
dump_info.win_name = plane->name;
dump_info.fb = fb;
dump_info.src = &pstate->src;
rockchip_drm_dump_plane_buffer(&dump_info, rockchip_crtc->vop_dump_frame_count);
}
rockchip_crtc->vop_dump_frame_count++;
return 0;
}
static int rockchip_drm_dump_buffer_show(struct seq_file *m, void *data)
{
seq_puts(m, "VOP dump buffer version: v2.0.0\n");
seq_puts(m, " echo dump > Immediately dump the current frame\n");
seq_puts(m, " echo dumpon > dump to start vop keep dumping\n");
seq_puts(m, " echo dumpoff > Disable dump feature and stop keep dumping\n");
seq_puts(m, " echo dumpn > dump n is the number of dump times\n");
seq_puts(m, " dump path is /data\n");
return 0;
}
static int rockchip_drm_dump_buffer_open(struct inode *inode, struct file *file)
{
struct drm_crtc *crtc = inode->i_private;
return single_open(file, rockchip_drm_dump_buffer_show, crtc);
}
static ssize_t
rockchip_drm_dump_buffer_write(struct file *file, const char __user *ubuf,
size_t len, loff_t *offp)
{
struct seq_file *m = file->private_data;
struct drm_crtc *crtc = m->private;
char buf[14] = {};
int dump_times = 0;
int i = 0;
struct rockchip_crtc *rockchip_crtc = to_rockchip_crtc(crtc);
if (len > sizeof(buf) - 1)
return -EINVAL;
if (copy_from_user(buf, ubuf, len))
return -EFAULT;
buf[len - 1] = '\0';
if (strncmp(buf, "dumpon", 6) == 0) {
rockchip_crtc->vop_dump_status = DUMP_KEEP;
DRM_INFO("keep dumping\n");
} else if (strncmp(buf, "dumpoff", 7) == 0) {
rockchip_crtc->vop_dump_status = DUMP_DISABLE;
DRM_INFO("close keep dumping\n");
} else if (strncmp(buf, "dump", 4) == 0) {
if (isdigit(buf[4])) {
for (i = 4; i < strlen(buf); i++) {
dump_times += temp_pow(10, (strlen(buf)
- i - 1))
* (buf[i] - '0');
}
rockchip_crtc->vop_dump_times = dump_times;
} else {
drm_modeset_lock_all(crtc->dev);
rockchip_drm_crtc_dump_plane_buffer(crtc);
drm_modeset_unlock_all(crtc->dev);
}
} else {
return -EINVAL;
}
return len;
}
static const struct file_operations rockchip_drm_dump_buffer_fops = {
.owner = THIS_MODULE,
.open = rockchip_drm_dump_buffer_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.write = rockchip_drm_dump_buffer_write,
};
int rockchip_drm_add_dump_buffer(struct drm_crtc *crtc, struct dentry *root)
{
struct dentry *vop_dump_root;
struct dentry *ent;
struct rockchip_crtc *rockchip_crtc = to_rockchip_crtc(crtc);
vop_dump_root = debugfs_create_dir("vop_dump", root);
rockchip_crtc->vop_dump_status = DUMP_DISABLE;
rockchip_crtc->vop_dump_times = 0;
rockchip_crtc->vop_dump_frame_count = 0;
ent = debugfs_create_file("dump", 0644, vop_dump_root,
crtc, &rockchip_drm_dump_buffer_fops);
if (!ent) {
DRM_ERROR("create vop_plane_dump err\n");
debugfs_remove_recursive(vop_dump_root);
}
return 0;
}
#endif
static int rockchip_drm_debugfs_color_bar_show(struct seq_file *s, void *data)
{
seq_puts(s, " Enable horizontal color bar:\n");
seq_puts(s, " echo 1 > /sys/kernel/debug/dri/0/video_port0/color_bar\n");
seq_puts(s, " Enable vertical color bar:\n");
seq_puts(s, " echo 2 > /sys/kernel/debug/dri/0/video_port0/color_bar\n");
seq_puts(s, " Disable color bar:\n");
seq_puts(s, " echo 0 > /sys/kernel/debug/dri/0/video_port0/color_bar\n");
return 0;
}
static int rockchip_drm_debugfs_color_bar_open(struct inode *inode, struct file *file)
{
struct drm_crtc *crtc = inode->i_private;
return single_open(file, rockchip_drm_debugfs_color_bar_show, crtc);
}
static ssize_t rockchip_drm_debugfs_color_bar_write(struct file *file, const char __user *ubuf,
size_t len, loff_t *offp)
{
struct seq_file *s = file->private_data;
struct drm_crtc *crtc = s->private;
struct rockchip_drm_private *priv = crtc->dev->dev_private;
int pipe = drm_crtc_index(crtc);
u8 mode;
if (len != 2) {
DRM_INFO("Unsupported color bar mode\n");
return -EINVAL;
}
if (kstrtou8_from_user(ubuf, len, 0, &mode))
return -EFAULT;
if (priv->crtc_funcs[pipe]->crtc_set_color_bar) {
if (priv->crtc_funcs[pipe]->crtc_set_color_bar(crtc, mode))
return -EINVAL;
}
return len;
}
static const struct file_operations rockchip_drm_debugfs_color_bar_fops = {
.owner = THIS_MODULE,
.open = rockchip_drm_debugfs_color_bar_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.write = rockchip_drm_debugfs_color_bar_write,
};
int rockchip_drm_debugfs_add_color_bar(struct drm_crtc *crtc, struct dentry *root)
{
struct dentry *ent;
ent = debugfs_create_file("color_bar", 0644, root, crtc,
&rockchip_drm_debugfs_color_bar_fops);
if (!ent)
DRM_ERROR("Failed to add color_bar for debugfs\n");
return 0;
}
static int rockchip_drm_debugfs_regs_write_show(struct seq_file *s, void *data)
{
seq_puts(s, " Write VOP regs:\n");
seq_puts(s, " echo address val > /sys/kernel/debug/dri/0/video_portx/regs_write\n\n");
seq_puts(s, " video_portx is depend on hardware config, you can get this info from the cmd:\n");
seq_puts(s, " cat /sys/kernel/debug/dri/0/summary\n\n");
seq_puts(s, " Example:\n");
seq_puts(s, " echo 0x27d00000 0x1 > /sys/kernel/debug/dri/0/video_portx/regs_write\n\n");
return 0;
}
static int rockchip_drm_debugfs_regs_write_open(struct inode *inode, struct file *file)
{
struct drm_crtc *crtc = inode->i_private;
return single_open(file, rockchip_drm_debugfs_regs_write_show, crtc);
}
static ssize_t rockchip_drm_debugfs_regs_write(struct file *file, const char __user *ubuf,
size_t len, loff_t *offp)
{
struct seq_file *s = file->private_data;
struct drm_crtc *crtc = s->private;
struct rockchip_drm_private *priv = crtc->dev->dev_private;
int ret = 0, pipe = drm_crtc_index(crtc);
unsigned long address = 0;
u32 val = 0;
char kbuf[32];
len = min_t(size_t, len, (sizeof(kbuf) - 1));
if (copy_from_user(kbuf, ubuf, len))
return -EINVAL;
kbuf[len] = 0;
if (sscanf(kbuf, "%lx %x", &address, &val) == -1)
return -EFAULT;
if (priv->crtc_funcs[pipe]->regs_write)
ret = priv->crtc_funcs[pipe]->regs_write(crtc, address, val);
if (ret)
return ret;
return len;
}
static const struct file_operations rockchip_drm_debugfs_regs_write_ops = {
.owner = THIS_MODULE,
.open = rockchip_drm_debugfs_regs_write_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.write = rockchip_drm_debugfs_regs_write,
};
int rockchip_drm_debugfs_add_regs_write(struct drm_crtc *crtc, struct dentry *root)
{
struct dentry *ent;
ent = debugfs_create_file("regs_write", 0644, root, crtc,
&rockchip_drm_debugfs_regs_write_ops);
if (!ent)
DRM_ERROR("Failed to add regs_write for debugfs\n");
return 0;
}