// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2021 Rockchip Electronics Co., Ltd. * Author: Sandy Huang */ #include #include #include #include #include #include #include #include #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; }