diff mbox series

[2/4] disp/msm/dpu: add support to dump dpu registers

Message ID 20201022050148.27105-3-abhinavk@codeaurora.org
State Superseded
Headers show
Series Add devcoredump support for DPU | expand

Commit Message

Abhinav Kumar Oct. 22, 2020, 5:01 a.m. UTC
Add the dpu_dbg module which adds supports to dump dpu registers
which can be used in case of error conditions.

Signed-off-by: Abhinav Kumar <abhinavk@codeaurora.org>
---
 drivers/gpu/drm/msm/Makefile                  |   2 +
 drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.c       | 316 ++++++++++++++++++
 drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.h       | 273 +++++++++++++++
 drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c  | 313 +++++++++++++++++
 .../gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h    |   4 +-
 drivers/gpu/drm/msm/msm_drv.c                 |   6 +-
 6 files changed, 912 insertions(+), 2 deletions(-)
 create mode 100644 drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.c
 create mode 100644 drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.h
 create mode 100644 drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c

Comments

kernel test robot Oct. 27, 2020, 2:15 p.m. UTC | #1
Hi Abhinav,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on drm-exynos/exynos-drm-next]
[also build test WARNING on drm-intel/for-linux-next tegra-drm/drm/tegra/for-next drm-tip/drm-tip linus/master v5.10-rc1 next-20201027]
[cannot apply to drm/drm-next]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Abhinav-Kumar/Add-devcoredump-support-for-DPU/20201022-130507
base:   https://git.kernel.org/pub/scm/linux/kernel/git/daeinki/drm-exynos.git exynos-drm-next
config: arm64-randconfig-s032-20201026 (attached as .config)
compiler: aarch64-linux-gcc (GCC) 9.3.0
reproduce:
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # apt-get install sparse
        # sparse version: v0.6.3-56-gc09e8239-dirty
        # https://github.com/0day-ci/linux/commit/a7e6907c303a46ea8422fc3c414c22fdfb45d49f
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Abhinav-Kumar/Add-devcoredump-support-for-DPU/20201022-130507
        git checkout a7e6907c303a46ea8422fc3c414c22fdfb45d49f
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__' ARCH=arm64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>


"sparse warnings: (new ones prefixed by >>)"
>> drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:99:50: sparse: sparse: incorrect type in argument 1 (different address spaces) @@     expected void const volatile [noderef] __iomem *addr @@     got char * @@
>> drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:99:50: sparse:     expected void const volatile [noderef] __iomem *addr
>> drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:99:50: sparse:     got char *
   drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:100:56: sparse: sparse: incorrect type in argument 1 (different address spaces) @@     expected void const volatile [noderef] __iomem *addr @@     got char * @@
   drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:100:56: sparse:     expected void const volatile [noderef] __iomem *addr
   drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:100:56: sparse:     got char *
   drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:102:56: sparse: sparse: incorrect type in argument 1 (different address spaces) @@     expected void const volatile [noderef] __iomem *addr @@     got char * @@
   drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:102:56: sparse:     expected void const volatile [noderef] __iomem *addr
   drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:102:56: sparse:     got char *
   drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:104:56: sparse: sparse: incorrect type in argument 1 (different address spaces) @@     expected void const volatile [noderef] __iomem *addr @@     got char * @@
   drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:104:56: sparse:     expected void const volatile [noderef] __iomem *addr
   drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:104:56: sparse:     got char *
>> drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:211:30: sparse: sparse: incorrect type in assignment (different address spaces) @@     expected char *addr @@     got void [noderef] __iomem * @@
>> drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:211:30: sparse:     expected char *addr
>> drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:211:30: sparse:     got void [noderef] __iomem *
>> drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:220:44: sparse: sparse: incorrect type in argument 4 (different address spaces) @@     expected char *base_addr @@     got void [noderef] __iomem *base @@
>> drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:220:44: sparse:     expected char *base_addr
>> drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:220:44: sparse:     got void [noderef] __iomem *base
>> drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:229:22: sparse: sparse: incorrect type in assignment (different address spaces) @@     expected char *addr @@     got void [noderef] __iomem *base @@
   drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:229:22: sparse:     expected char *addr
   drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:229:22: sparse:     got void [noderef] __iomem *base
   drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:232:36: sparse: sparse: incorrect type in argument 4 (different address spaces) @@     expected char *base_addr @@     got void [noderef] __iomem *base @@
   drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:232:36: sparse:     expected char *base_addr
   drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c:232:36: sparse:     got void [noderef] __iomem *base

vim +99 drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c

    20	
    21	/**
    22	 * _dpu_dump_reg - helper function for dumping rotator register set content
    23	 * @dump_name: register set name
    24	 * @reg_dump_flag: dumping flag controlling in-log/memory dump location
    25	 * @base_addr: starting address of io region for calculating offsets to print
    26	 * @addr: starting address offset for dumping
    27	 * @len_bytes: range of the register set
    28	 * @dump_mem: output buffer for memory dump location option
    29	 * @from_isr: whether being called from isr context
    30	 */
    31	static void _dpu_dump_reg(struct dpu_dbg_base *dbg_base,
    32			const char *dump_name, u32 reg_dump_flag,
    33			char *base_addr, char *addr, size_t len_bytes, u32 **dump_mem)
    34	{
    35		u32 in_log, in_mem, len_align, len_padded, in_dump;
    36		u32 *dump_addr = NULL;
    37		char *end_addr;
    38		int i;
    39		int rc;
    40	
    41		if (!len_bytes)
    42			return;
    43	
    44		in_log = (reg_dump_flag & DPU_DBG_DUMP_IN_LOG);
    45		in_mem = (reg_dump_flag & DPU_DBG_DUMP_IN_MEM);
    46		in_dump = (reg_dump_flag & DPU_DBG_DUMP_IN_COREDUMP);
    47	
    48		pr_debug("%s: reg_dump_flag=%d in_log=%d in_mem=%d\n",
    49			dump_name, reg_dump_flag, in_log, in_mem);
    50	
    51		if (!in_log && !in_mem && !in_dump)
    52			return;
    53	
    54		if (in_log)
    55			dev_info(dbg_base->dev, "%s: start_offset 0x%lx len 0x%zx\n",
    56					dump_name, (unsigned long)(addr - base_addr),
    57						len_bytes);
    58	
    59		len_align = (len_bytes + REG_DUMP_ALIGN - 1) / REG_DUMP_ALIGN;
    60		len_padded = len_align * REG_DUMP_ALIGN;
    61		end_addr = addr + len_bytes;
    62	
    63		if (in_mem || in_dump) {
    64			if (dump_mem && !(*dump_mem))
    65				*dump_mem = devm_kzalloc(dbg_base->dev, len_padded,
    66						GFP_KERNEL);
    67	
    68			if (dump_mem && *dump_mem) {
    69				dump_addr = *dump_mem;
    70				dev_info(dbg_base->dev,
    71					"%s: start_addr:0x%pK len:0x%x reg_offset=0x%lx\n",
    72					dump_name, dump_addr, len_padded,
    73					(unsigned long)(addr - base_addr));
    74				if (in_dump)
    75					drm_printf(dbg_base->dpu_dbg_printer,
    76							"%s: start_addr:0x%pK len:0x%x reg_offset=0x%lx\n",
    77							dump_name, dump_addr,
    78							len_padded,
    79							(unsigned long)(addr -
    80							base_addr));
    81			} else {
    82				in_mem = 0;
    83				pr_err("dump_mem: kzalloc fails!\n");
    84			}
    85		}
    86	
    87		if (_dpu_power_check(dbg_base->dump_mode)) {
    88			rc = pm_runtime_get_sync(dbg_base->dev);
    89			if (rc < 0) {
    90				pr_err("failed to enable power %d\n", rc);
    91				return;
    92			}
    93		}
    94	
    95		for (i = 0; i < len_align; i++) {
    96			u32 x0, x4, x8, xc;
    97	
    98			if (in_log || in_mem) {
  > 99				x0 = (addr < end_addr) ? readl_relaxed(addr + 0x0) : 0;
   100				x4 = (addr + 0x4 < end_addr) ? readl_relaxed(addr +
   101						0x4) : 0;
   102				x8 = (addr + 0x8 < end_addr) ? readl_relaxed(addr +
   103						0x8) : 0;
   104				xc = (addr + 0xc < end_addr) ? readl_relaxed(addr +
   105						0xc) : 0;
   106			}
   107	
   108			if (in_log)
   109				dev_info(dbg_base->dev,
   110						"0x%lx : %08x %08x %08x %08x\n",
   111						(unsigned long)(addr - base_addr),
   112						x0, x4, x8, xc);
   113	
   114			if (dump_addr && in_mem) {
   115				dump_addr[i * 4] = x0;
   116				dump_addr[i * 4 + 1] = x4;
   117				dump_addr[i * 4 + 2] = x8;
   118				dump_addr[i * 4 + 3] = xc;
   119			}
   120	
   121			if (in_dump) {
   122				drm_printf(dbg_base->dpu_dbg_printer,
   123						"0x%lx : %08x %08x %08x %08x\n",
   124						(unsigned long)(addr - base_addr),
   125						dump_addr[i * 4],
   126						dump_addr[i * 4 + 1],
   127						dump_addr[i * 4 + 2],
   128						dump_addr[i * 4 + 3]);
   129	
   130			}
   131	
   132			addr += REG_DUMP_ALIGN;
   133		}
   134	
   135		if (_dpu_power_check(dbg_base->dump_mode))
   136			pm_runtime_put_sync(dbg_base->dev);
   137	}
   138	
   139	/**
   140	 * _dpu_dbg_get_dump_range - helper to retrieve dump length for a range node
   141	 * @range_node: range node to dump
   142	 * @max_offset: max offset of the register base
   143	 * @Return: length
   144	 */
   145	static u32 _dpu_dbg_get_dump_range(struct dpu_dbg_reg_offset *range_node,
   146			size_t max_offset)
   147	{
   148		u32 length = 0;
   149	
   150		if (range_node->start == 0 && range_node->end == 0) {
   151			length = max_offset;
   152		} else if (range_node->start < max_offset) {
   153			if (range_node->end > max_offset)
   154				length = max_offset - range_node->start;
   155			else if (range_node->start < range_node->end)
   156				length = range_node->end - range_node->start;
   157		}
   158	
   159		return length;
   160	}
   161	
   162	static int _dpu_dump_reg_range_cmp(void *priv, struct list_head *a,
   163			struct list_head *b)
   164	{
   165		struct dpu_dbg_reg_range *ar, *br;
   166	
   167		if (!a || !b)
   168			return 0;
   169	
   170		ar = container_of(a, struct dpu_dbg_reg_range, head);
   171		br = container_of(b, struct dpu_dbg_reg_range, head);
   172	
   173		return ar->offset.start - br->offset.start;
   174	}
   175	
   176	/**
   177	 * _dpu_dump_reg_by_ranges - dump ranges or full range of the register blk base
   178	 * @dbg: register blk base structure
   179	 * @reg_dump_flag: dump target, memory, kernel log, or both
   180	 */
   181	static void _dpu_dump_reg_by_ranges(struct dpu_dbg_base *dbg_base,
   182			struct dpu_dbg_reg_base *dbg,
   183			u32 reg_dump_flag)
   184	{
   185		char *addr;
   186		size_t len;
   187		struct dpu_dbg_reg_range *range_node;
   188	
   189		if (!dbg || !(dbg->base || dbg->cb)) {
   190			pr_err("dbg base is null!\n");
   191			return;
   192		}
   193	
   194		dev_info(dbg_base->dev, "%s:=========%s DUMP=========\n", __func__,
   195				dbg->name);
   196	
   197		if (reg_dump_flag & DPU_DBG_DUMP_IN_COREDUMP)
   198			drm_printf(dbg_base->dpu_dbg_printer,
   199					"%s:=========%s DUMP=========\n",
   200					__func__, dbg->name);
   201	
   202		if (dbg->cb) {
   203			dbg->cb(dbg->cb_ptr);
   204		/* If there is a list to dump the registers by ranges, use the ranges */
   205		} else if (!list_empty(&dbg->sub_range_list)) {
   206			/* sort the list by start address first */
   207			list_sort(NULL, &dbg->sub_range_list, _dpu_dump_reg_range_cmp);
   208			list_for_each_entry(range_node, &dbg->sub_range_list, head) {
   209				len = _dpu_dbg_get_dump_range(&range_node->offset,
   210					dbg->max_offset);
 > 211				addr = dbg->base + range_node->offset.start;
   212	
   213				pr_debug("%s: range_base=0x%pK start=0x%x end=0x%x\n",
   214					range_node->range_name,
   215					addr, range_node->offset.start,
   216					range_node->offset.end);
   217	
   218				_dpu_dump_reg(dbg_base, range_node->range_name,
   219						reg_dump_flag,
 > 220						dbg->base, addr, len,
   221						&range_node->reg_dump);
   222			}
   223		} else {
   224			/* If there is no list to dump ranges, dump all registers */
   225			dev_info(dbg_base->dev,
   226					"Ranges not found, will dump full registers\n");
   227			dev_info(dbg_base->dev, "base:0x%pK len:0x%zx\n", dbg->base,
   228					dbg->max_offset);
 > 229			addr = dbg->base;
   230			len = dbg->max_offset;
   231			_dpu_dump_reg(dbg_base, dbg->name, reg_dump_flag,
   232					dbg->base, addr, len,
   233					&dbg->reg_dump);
   234		}
   235	}
   236	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
diff mbox series

Patch

diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 340682cd0f32..96bd1398edac 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -54,6 +54,8 @@  msm-y := \
 	disp/dpu1/dpu_core_irq.o \
 	disp/dpu1/dpu_core_perf.o \
 	disp/dpu1/dpu_crtc.o \
+	disp/dpu1/dpu_dbg.o \
+	disp/dpu1/dpu_dbg_util.o \
 	disp/dpu1/dpu_encoder.o \
 	disp/dpu1/dpu_encoder_phys_cmd.o \
 	disp/dpu1/dpu_encoder_phys_vid.o \
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.c
new file mode 100644
index 000000000000..6703e1555194
--- /dev/null
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.c
@@ -0,0 +1,316 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2009-2020, The Linux Foundation. All rights reserved.
+ */
+
+#define pr_fmt(fmt)	"[drm:%s:%d] " fmt, __func__, __LINE__
+
+#include "dpu_dbg.h"
+#include "dpu_hw_catalog.h"
+
+/* global dpu debug base structure */
+static struct dpu_dbg_base dpu_dbg;
+
+
+#ifdef CONFIG_DEV_COREDUMP
+static ssize_t dpu_devcoredump_read(char *buffer, loff_t offset,
+		size_t count, void *data, size_t datalen)
+{
+	struct drm_print_iterator iter;
+	struct drm_printer p;
+
+	iter.data = buffer;
+	iter.offset = 0;
+	iter.start = offset;
+	iter.remain = count;
+
+	p = drm_coredump_printer(&iter);
+
+	drm_printf(&p, "---\n");
+
+	drm_printf(&p, "module: " KBUILD_MODNAME "\n");
+	drm_printf(&p, "dpu devcoredump\n");
+	drm_printf(&p, "timestamp %lld\n", ktime_to_ns(dpu_dbg.timestamp));
+
+	dpu_dbg.dpu_dbg_printer = &p;
+	dpu_dbg.enable_reg_dump = DPU_DBG_DUMP_IN_COREDUMP;
+
+	drm_printf(&p, "===================dpu regs================\n");
+
+	_dpu_dump_array(&dpu_dbg, dpu_dbg.req_dump_blks,
+		ARRAY_SIZE(dpu_dbg.req_dump_blks),
+		dpu_dbg.work_panic, "evtlog_workitem",
+		dpu_dbg.dump_all);
+
+	drm_printf(&p, "===================dpu drm state================\n");
+
+	if (dpu_dbg.atomic_state)
+		drm_atomic_print_state(dpu_dbg.atomic_state,
+				&p);
+
+	return count - iter.remain;
+}
+
+static void dpu_devcoredump_free(void *data)
+{
+	if (dpu_dbg.atomic_state) {
+		drm_atomic_state_put(dpu_dbg.atomic_state);
+		dpu_dbg.atomic_state = NULL;
+	}
+	dpu_dbg.coredump_pending = false;
+}
+
+static void dpu_devcoredump_capture_state(void)
+{
+	struct drm_device *ddev;
+	struct drm_modeset_acquire_ctx ctx;
+
+	dpu_dbg.timestamp = ktime_get();
+
+	ddev = dpu_dbg.drm_dev;
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+	while (drm_modeset_lock_all_ctx(ddev, &ctx) != 0)
+		drm_modeset_backoff(&ctx);
+
+	dpu_dbg.atomic_state = drm_atomic_helper_duplicate_state(ddev,
+			&ctx);
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+#else
+static void dpu_devcoredump_capture_state(void)
+{
+}
+#endif /* CONFIG_DEV_COREDUMP */
+
+/**
+ * _dpu_dump_work - deferred dump work function
+ * @work: work structure
+ */
+static void _dpu_dump_work(struct kthread_work *work)
+{
+	/* reset the enable_reg_dump to default before every dump */
+	dpu_dbg.enable_reg_dump = DEFAULT_REGDUMP;
+
+	_dpu_dump_array(&dpu_dbg, dpu_dbg.req_dump_blks,
+		ARRAY_SIZE(dpu_dbg.req_dump_blks),
+		dpu_dbg.work_panic, "evtlog_workitem",
+		dpu_dbg.dump_all);
+
+	dpu_devcoredump_capture_state();
+
+#ifdef CONFIG_DEV_COREDUMP
+	if (dpu_dbg.enable_reg_dump & DPU_DBG_DUMP_IN_MEM) {
+		dev_coredumpm(dpu_dbg.dev, THIS_MODULE, &dpu_dbg, 0, GFP_KERNEL,
+				dpu_devcoredump_read, dpu_devcoredump_free);
+		dpu_dbg.coredump_pending = true;
+	}
+#endif
+}
+
+void dpu_dbg_dump(enum dpu_dbg_dump_context dump_mode, const char *name, ...)
+{
+	int i, index = 0;
+	bool do_panic = false;
+	bool dump_all = false;
+	va_list args;
+	char *blk_name = NULL;
+	struct dpu_dbg_reg_base *blk_base = NULL;
+	struct dpu_dbg_reg_base **blk_arr;
+	u32 blk_len;
+
+	/*
+	 * if there is a coredump pending return immediately till dump
+	 * if read by userspace or timeout happens
+	 */
+	if (((dpu_dbg.enable_reg_dump == DPU_DBG_DUMP_IN_MEM) ||
+		 (dpu_dbg.enable_reg_dump == DPU_DBG_DUMP_IN_COREDUMP)) &&
+		dpu_dbg.coredump_pending) {
+		pr_debug("coredump is pending read\n");
+		return;
+	}
+
+	blk_arr = &dpu_dbg.req_dump_blks[0];
+	blk_len = ARRAY_SIZE(dpu_dbg.req_dump_blks);
+
+	memset(dpu_dbg.req_dump_blks, 0,
+			sizeof(dpu_dbg.req_dump_blks));
+	dpu_dbg.dump_all = false;
+	dpu_dbg.dump_mode = dump_mode;
+
+	va_start(args, name);
+	i = 0;
+	while ((blk_name = va_arg(args, char*))) {
+
+		if (IS_ERR_OR_NULL(blk_name))
+			break;
+
+		blk_base = _dpu_dump_get_blk_addr(&dpu_dbg, blk_name);
+		if (blk_base) {
+			if (index < blk_len) {
+				blk_arr[index] = blk_base;
+				index++;
+			} else {
+				pr_err("insufficient space to dump %s\n",
+						blk_name);
+			}
+		}
+
+		if (!strcmp(blk_name, "all"))
+			dump_all = true;
+
+		if (!strcmp(blk_name, "panic"))
+			do_panic = true;
+
+	}
+	va_end(args);
+
+	dpu_dbg.work_panic = do_panic;
+	dpu_dbg.dump_all = dump_all;
+
+	kthread_queue_work(dpu_dbg.dump_worker,
+			&dpu_dbg.dump_work);
+
+}
+
+int dpu_dbg_init(struct device *dev)
+{
+	if (!dev) {
+		pr_err("invalid params\n");
+		return -EINVAL;
+	}
+
+	mutex_init(&dpu_dbg.mutex);
+	INIT_LIST_HEAD(&dpu_dbg.reg_base_list);
+	dpu_dbg.dev = dev;
+
+	dpu_dbg.work_panic = false;
+	dpu_dbg.enable_reg_dump = DEFAULT_REGDUMP;
+
+	dpu_dbg.dump_worker = kthread_create_worker(0, "%s", "dpu_dbg");
+	if (IS_ERR(dpu_dbg.dump_worker))
+		dev_err(dev, "failed to create dpu dbg task\n");
+
+	kthread_init_work(&dpu_dbg.dump_work, _dpu_dump_work);
+
+	pr_info("dump:%d\n", dpu_dbg.enable_reg_dump);
+
+	return 0;
+}
+
+void dpu_dbg_register_drm_dev(struct drm_device *ddev)
+{
+	dpu_dbg.drm_dev = ddev;
+}
+
+static void dpu_dbg_reg_base_destroy(void)
+{
+	struct dpu_dbg_reg_range *range_node, *range_tmp;
+	struct dpu_dbg_reg_base *blk_base, *blk_tmp;
+	struct dpu_dbg_base *dbg_base = &dpu_dbg;
+
+	/* if the dbg init failed or was never called */
+	if (!dbg_base || !dpu_dbg.dev)
+		return;
+
+	list_for_each_entry_safe(blk_base, blk_tmp, &dbg_base->reg_base_list,
+							reg_base_head) {
+		list_for_each_entry_safe(range_node, range_tmp,
+				&blk_base->sub_range_list, head) {
+			list_del(&range_node->head);
+			kfree(range_node);
+		}
+		list_del(&blk_base->reg_base_head);
+		kfree(blk_base);
+	}
+}
+
+/**
+ * dpu_dbg_destroy - destroy dpu debug facilities
+ */
+void dpu_dbg_destroy(void)
+{
+	if (dpu_dbg.dump_worker)
+		kthread_destroy_worker(dpu_dbg.dump_worker);
+	dpu_dbg_reg_base_destroy();
+	mutex_destroy(&dpu_dbg.mutex);
+}
+
+int dpu_dbg_reg_register_base(const char *name, void __iomem *base,
+		size_t max_offset)
+{
+	struct dpu_dbg_base *dbg_base = &dpu_dbg;
+	struct dpu_dbg_reg_base *reg_base;
+
+	if (!name || !strlen(name)) {
+		pr_err("no debug name provided\n");
+		return -EINVAL;
+	}
+
+	reg_base = kzalloc(sizeof(*reg_base), GFP_KERNEL);
+	if (!reg_base)
+		return -ENOMEM;
+
+	strlcpy(reg_base->name, name, sizeof(reg_base->name));
+	reg_base->base = base;
+	reg_base->max_offset = max_offset;
+	reg_base->off = 0;
+	reg_base->cnt = DEFAULT_BASE_REG_CNT;
+	reg_base->reg_dump = NULL;
+
+	/* Initialize list to make sure check for null list will be valid */
+	INIT_LIST_HEAD(&reg_base->sub_range_list);
+
+	pr_debug("%s base: %pK max_offset 0x%zX\n", reg_base->name,
+			reg_base->base, reg_base->max_offset);
+
+	list_add(&reg_base->reg_base_head, &dbg_base->reg_base_list);
+
+	return 0;
+}
+
+void dpu_dbg_reg_register_dump_range(const char *base_name,
+		const char *range_name, u32 offset_start, u32 offset_end,
+		uint32_t xin_id)
+{
+	struct dpu_dbg_reg_base *reg_base;
+	struct dpu_dbg_reg_range *range;
+
+	reg_base = _dpu_dump_get_blk_addr(&dpu_dbg, base_name);
+	if (!reg_base) {
+		pr_err("error: for range %s unable to locate base %s\n",
+				range_name, base_name);
+		return;
+	}
+
+	if (!range_name || strlen(range_name) == 0) {
+		pr_err("%pS: bad range name, base_name %s, offset_start 0x%X, end 0x%X\n",
+				__builtin_return_address(0), base_name,
+				offset_start, offset_end);
+		return;
+	}
+
+	if (offset_end - offset_start < REG_DUMP_ALIGN ||
+			offset_start > offset_end) {
+		pr_err("%pS: bad range, base_name %s, range_name %s, offset_start 0x%X, end 0x%X\n",
+				__builtin_return_address(0), base_name,
+				range_name, offset_start, offset_end);
+		return;
+	}
+
+	range = kzalloc(sizeof(*range), GFP_KERNEL);
+	if (!range)
+		return;
+
+	strlcpy(range->range_name, range_name, sizeof(range->range_name));
+	range->offset.start = offset_start;
+	range->offset.end = offset_end;
+	range->xin_id = xin_id;
+	list_add_tail(&range->head, &reg_base->sub_range_list);
+
+	pr_debug("base %s, range %s, start 0x%X, end 0x%X\n",
+			base_name, range->range_name,
+			range->offset.start, range->offset.end);
+}
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.h
new file mode 100644
index 000000000000..2cea13288cef
--- /dev/null
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.h
@@ -0,0 +1,273 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef DPU_DBG_H_
+#define DPU_DBG_H_
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_device.h>
+#include "../../../drm_crtc_internal.h"
+#include <drm/drm_print.h>
+#include <drm/drm_atomic.h>
+#include <linux/debugfs.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/ktime.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include <linux/dma-buf.h>
+#include <linux/slab.h>
+#include <linux/list_sort.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/kthread.h>
+#include <linux/devcoredump.h>
+#include <stdarg.h>
+
+#define DPU_DBG_DUMP_DATA_LIMITER (NULL)
+
+enum dpu_dbg_dump_flag {
+	DPU_DBG_DUMP_IN_LOG = BIT(0),
+	DPU_DBG_DUMP_IN_MEM = BIT(1),
+	DPU_DBG_DUMP_IN_COREDUMP = BIT(2),
+};
+
+enum dpu_dbg_dump_context {
+	DPU_DBG_DUMP_PROC_CTX,
+	DPU_DBG_DUMP_CLK_ENABLED_CTX,
+};
+
+#define DPU_DBG_BASE_MAX		10
+
+#define DEFAULT_PANIC		0
+#define DEFAULT_REGDUMP		DPU_DBG_DUMP_IN_MEM
+#define DEFAULT_BASE_REG_CNT	DEFAULT_MDSS_HW_BLOCK_SIZE
+#define ROW_BYTES		16
+#define RANGE_NAME_LEN		40
+#define REG_BASE_NAME_LEN	80
+
+/* print debug ranges in groups of 4 u32s */
+#define REG_DUMP_ALIGN		16
+
+/**
+ * struct dpu_dbg_reg_offset - tracking for start and end of region
+ * @start: start offset
+ * @start: end offset
+ */
+struct dpu_dbg_reg_offset {
+	u32 start;
+	u32 end;
+};
+
+/**
+ * struct dpu_dbg_reg_range - register dumping named sub-range
+ * @head: head of this node
+ * @reg_dump: address for the mem dump
+ * @range_name: name of this range
+ * @offset: offsets for range to dump
+ * @xin_id: client xin id
+ */
+struct dpu_dbg_reg_range {
+	struct list_head head;
+	u32 *reg_dump;
+	char range_name[RANGE_NAME_LEN];
+	struct dpu_dbg_reg_offset offset;
+	uint32_t xin_id;
+};
+
+/**
+ * struct dpu_dbg_reg_base - register region base.
+ *	may sub-ranges: sub-ranges are used for dumping
+ *	or may not have sub-ranges: dumping is base -> max_offset
+ * @reg_base_head: head of this node
+ * @sub_range_list: head to the list with dump ranges
+ * @name: register base name
+ * @base: base pointer
+ * @off: cached offset of region for manual register dumping
+ * @cnt: cached range of region for manual register dumping
+ * @max_offset: length of region
+ * @buf: buffer used for manual register dumping
+ * @buf_len:  buffer length used for manual register dumping
+ * @reg_dump: address for the mem dump if no ranges used
+ * @cb: callback for external dump function, null if not defined
+ * @cb_ptr: private pointer to callback function
+ */
+struct dpu_dbg_reg_base {
+	struct list_head reg_base_head;
+	struct list_head sub_range_list;
+	char name[REG_BASE_NAME_LEN];
+	void __iomem *base;
+	size_t off;
+	size_t cnt;
+	size_t max_offset;
+	char *buf;
+	size_t buf_len;
+	u32 *reg_dump;
+	void (*cb)(void *ptr);
+	void *cb_ptr;
+};
+
+/**
+ * struct dpu_dbg_base - global sde debug base structure
+ * @evtlog: event log instance
+ * @reg_base_list: list of register dumping regions
+ * @dev: device pointer
+ * @drm_dev: drm device pointer
+ * @mutex: mutex to serialize access to serialze dumps, debugfs access
+ * @req_dump_blks: list of blocks requested for dumping
+ * @work_panic: panic after dump if internal user passed "panic" special region
+ * @enable_reg_dump: whether to dump registers into memory, kernel log, or both
+ * @dump_all: dump all entries in register dump
+ * @coredump_pending: coredump is pending read from userspace
+ * @atomic_state: atomic state duplicated at the time of the error
+ * @dump_worker: kworker thread which runs the dump work
+ * @dump_work: kwork which dumps the registers and drm state
+ * @timestamp: timestamp at which the coredump was captured
+ * @dpu_dbg_printer: drm printer handle used to take drm snapshot
+ * @dump_mode: decides whether the data is dumped in memory or logs
+ */
+struct dpu_dbg_base {
+	struct list_head reg_base_list;
+	struct device *dev;
+	struct drm_device *drm_dev;
+	struct mutex mutex;
+
+	struct dpu_dbg_reg_base *req_dump_blks[DPU_DBG_BASE_MAX];
+
+	bool work_panic;
+	u32 enable_reg_dump;
+
+	bool dump_all;
+	bool coredump_pending;
+
+	struct drm_atomic_state *atomic_state;
+
+	struct kthread_worker *dump_worker;
+	struct kthread_work dump_work;
+	ktime_t timestamp;
+
+	struct drm_printer *dpu_dbg_printer;
+
+	enum dpu_dbg_dump_context dump_mode;
+};
+
+struct dpu_dbg_power_ctrl {
+	void *handle;
+	void *client;
+	int (*enable_fn)(void *handle, void *client, bool enable);
+};
+
+
+/**
+ * DPU_DBG_DUMP - trigger dumping of all dpu_dbg facilities
+ * @va_args:	list of named register dump ranges and regions to dump, as
+ *		registered previously through dpu_dbg_reg_register_base and
+ *		dpu_dbg_reg_register_dump_range.
+ *		Including the special name "panic" will trigger a panic after
+ *		the dumping work has completed.
+ */
+#define DPU_DBG_DUMP(...) dpu_dbg_dump(DPU_DBG_DUMP_PROC_CTX, __func__, \
+		##__VA_ARGS__, DPU_DBG_DUMP_DATA_LIMITER)
+
+/**
+ * DPU_DBG_DUMP_CLK_EN - trigger dumping of all dpu_dbg facilities, without clk
+ * @va_args:	list of named register dump ranges and regions to dump, as
+ *		registered previously through dpu_dbg_reg_register_base and
+ *		dpu_dbg_reg_register_dump_range.
+ *		Including the special name "panic" will trigger a panic after
+ *		the dumping work has completed.
+ */
+#define DPU_DBG_DUMP_CLK_EN(...) dpu_dbg_dump(DPU_DBG_DUMP_CLK_ENABLED_CTX, \
+		__func__, ##__VA_ARGS__, DPU_DBG_DUMP_DATA_LIMITER)
+
+/**
+ * dpu_dbg_init - initialize global sde debug facilities: evtlog, regdump
+ * @dev:		device handle
+ * Returns:		0 or -ERROR
+ */
+int dpu_dbg_init(struct device *dev);
+
+/**
+ * dpu_dbg_register_drm_dev - register a drm device with the dpu dbg module
+ * @ddev:		drm device hangle
+ * Returns:		void
+ */
+void dpu_dbg_register_drm_dev(struct drm_device *ddev);
+
+/**
+ * dpu_dbg_destroy - destroy the global sde debug facilities
+ * Returns:	none
+ */
+void dpu_dbg_destroy(void);
+
+/**
+ * dpu_dbg_dump - trigger dumping of all dpu_dbg facilities
+ * @queue_work:	whether to queue the dumping work to the work_struct
+ * @name:	string indicating origin of dump
+ * @va_args:	list of named register dump ranges and regions to dump, as
+ *		registered previously through dpu_dbg_reg_register_base and
+ *		dpu_dbg_reg_register_dump_range.
+ *		Including the special name "panic" will trigger a panic after
+ *		the dumping work has completed.
+ * Returns:	none
+ */
+void dpu_dbg_dump(enum dpu_dbg_dump_context mode, const char *name, ...);
+
+/**
+ * dpu_dbg_reg_register_base - register a hw register address section for later
+ *	dumping. call this before calling dpu_dbg_reg_register_dump_range
+ *	to be able to specify sub-ranges within the base hw range.
+ * @name:	name of base region
+ * @base:	base pointer of region
+ * @max_offset:	length of region
+ * Returns:	0 or -ERROR
+ */
+int dpu_dbg_reg_register_base(const char *name, void __iomem *base,
+		size_t max_offset);
+
+/**
+ * dpu_dbg_reg_register_dump_range - register a hw register sub-region for
+ *	later register dumping associated with base specified by
+ *	dpu_dbg_reg_register_base
+ * @base_name:		name of base region
+ * @range_name:		name of sub-range within base region
+ * @offset_start:	sub-range's start offset from base's base pointer
+ * @offset_end:		sub-range's end offset from base's base pointer
+ * @xin_id:		xin id
+ * Returns:		none
+ */
+void dpu_dbg_reg_register_dump_range(const char *base_name,
+		const char *range_name, u32 offset_start, u32 offset_end,
+		uint32_t xin_id);
+
+/**
+ * dpu_dbg_set_sde_top_offset - set the target specific offset from mdss base
+ *	address of the top registers. Used for accessing debug bus controls.
+ * @blk_off: offset from mdss base of the top block
+ */
+void dpu_dbg_set_sde_top_offset(u32 blk_off);
+
+/**
+ * _dpu_dump_array - dump array of register bases
+ * @blk_arr: array of register base pointers
+ * @len: length of blk_arr
+ * @do_panic: whether to trigger a panic after dumping
+ * @name: string indicating origin of dump
+ * @dump_all: dump all regs
+ */
+void _dpu_dump_array(struct dpu_dbg_base *dbg_base,
+		struct dpu_dbg_reg_base *blk_arr[],
+		u32 len, bool do_panic, const char *name, bool dump_all);
+
+/**
+ * _dpu_dump_get_blk_addr - retrieve register block address by name
+ * @blk_name: register blk name
+ * @Return: register blk base, or NULL
+ */
+struct dpu_dbg_reg_base *_dpu_dump_get_blk_addr(struct dpu_dbg_base *dbg_base,
+		const char *blk_name);
+
+#endif /* DPU_DBG_H_ */
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c
new file mode 100644
index 000000000000..a54bea108020
--- /dev/null
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c
@@ -0,0 +1,313 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020, The Linux Foundation. All rights reserved.
+ */
+
+#define pr_fmt(fmt)	"[drm:%s:%d] " fmt, __func__, __LINE__
+
+#include "dpu_dbg.h"
+#include "dpu_hw_catalog.h"
+
+/**
+ * _sde_power_check - check if power needs to enabled
+ * @dump_mode: to check if power need to be enabled
+ * Return: true if success; false otherwise
+ */
+static inline bool _dpu_power_check(enum dpu_dbg_dump_context dump_mode)
+{
+	return (dump_mode == DPU_DBG_DUMP_CLK_ENABLED_CTX) ? false : true;
+}
+
+/**
+ * _dpu_dump_reg - helper function for dumping rotator register set content
+ * @dump_name: register set name
+ * @reg_dump_flag: dumping flag controlling in-log/memory dump location
+ * @base_addr: starting address of io region for calculating offsets to print
+ * @addr: starting address offset for dumping
+ * @len_bytes: range of the register set
+ * @dump_mem: output buffer for memory dump location option
+ * @from_isr: whether being called from isr context
+ */
+static void _dpu_dump_reg(struct dpu_dbg_base *dbg_base,
+		const char *dump_name, u32 reg_dump_flag,
+		char *base_addr, char *addr, size_t len_bytes, u32 **dump_mem)
+{
+	u32 in_log, in_mem, len_align, len_padded, in_dump;
+	u32 *dump_addr = NULL;
+	char *end_addr;
+	int i;
+	int rc;
+
+	if (!len_bytes)
+		return;
+
+	in_log = (reg_dump_flag & DPU_DBG_DUMP_IN_LOG);
+	in_mem = (reg_dump_flag & DPU_DBG_DUMP_IN_MEM);
+	in_dump = (reg_dump_flag & DPU_DBG_DUMP_IN_COREDUMP);
+
+	pr_debug("%s: reg_dump_flag=%d in_log=%d in_mem=%d\n",
+		dump_name, reg_dump_flag, in_log, in_mem);
+
+	if (!in_log && !in_mem && !in_dump)
+		return;
+
+	if (in_log)
+		dev_info(dbg_base->dev, "%s: start_offset 0x%lx len 0x%zx\n",
+				dump_name, (unsigned long)(addr - base_addr),
+					len_bytes);
+
+	len_align = (len_bytes + REG_DUMP_ALIGN - 1) / REG_DUMP_ALIGN;
+	len_padded = len_align * REG_DUMP_ALIGN;
+	end_addr = addr + len_bytes;
+
+	if (in_mem || in_dump) {
+		if (dump_mem && !(*dump_mem))
+			*dump_mem = devm_kzalloc(dbg_base->dev, len_padded,
+					GFP_KERNEL);
+
+		if (dump_mem && *dump_mem) {
+			dump_addr = *dump_mem;
+			dev_info(dbg_base->dev,
+				"%s: start_addr:0x%pK len:0x%x reg_offset=0x%lx\n",
+				dump_name, dump_addr, len_padded,
+				(unsigned long)(addr - base_addr));
+			if (in_dump)
+				drm_printf(dbg_base->dpu_dbg_printer,
+						"%s: start_addr:0x%pK len:0x%x reg_offset=0x%lx\n",
+						dump_name, dump_addr,
+						len_padded,
+						(unsigned long)(addr -
+						base_addr));
+		} else {
+			in_mem = 0;
+			pr_err("dump_mem: kzalloc fails!\n");
+		}
+	}
+
+	if (_dpu_power_check(dbg_base->dump_mode)) {
+		rc = pm_runtime_get_sync(dbg_base->dev);
+		if (rc < 0) {
+			pr_err("failed to enable power %d\n", rc);
+			return;
+		}
+	}
+
+	for (i = 0; i < len_align; i++) {
+		u32 x0, x4, x8, xc;
+
+		if (in_log || in_mem) {
+			x0 = (addr < end_addr) ? readl_relaxed(addr + 0x0) : 0;
+			x4 = (addr + 0x4 < end_addr) ? readl_relaxed(addr +
+					0x4) : 0;
+			x8 = (addr + 0x8 < end_addr) ? readl_relaxed(addr +
+					0x8) : 0;
+			xc = (addr + 0xc < end_addr) ? readl_relaxed(addr +
+					0xc) : 0;
+		}
+
+		if (in_log)
+			dev_info(dbg_base->dev,
+					"0x%lx : %08x %08x %08x %08x\n",
+					(unsigned long)(addr - base_addr),
+					x0, x4, x8, xc);
+
+		if (dump_addr && in_mem) {
+			dump_addr[i * 4] = x0;
+			dump_addr[i * 4 + 1] = x4;
+			dump_addr[i * 4 + 2] = x8;
+			dump_addr[i * 4 + 3] = xc;
+		}
+
+		if (in_dump) {
+			drm_printf(dbg_base->dpu_dbg_printer,
+					"0x%lx : %08x %08x %08x %08x\n",
+					(unsigned long)(addr - base_addr),
+					dump_addr[i * 4],
+					dump_addr[i * 4 + 1],
+					dump_addr[i * 4 + 2],
+					dump_addr[i * 4 + 3]);
+
+		}
+
+		addr += REG_DUMP_ALIGN;
+	}
+
+	if (_dpu_power_check(dbg_base->dump_mode))
+		pm_runtime_put_sync(dbg_base->dev);
+}
+
+/**
+ * _dpu_dbg_get_dump_range - helper to retrieve dump length for a range node
+ * @range_node: range node to dump
+ * @max_offset: max offset of the register base
+ * @Return: length
+ */
+static u32 _dpu_dbg_get_dump_range(struct dpu_dbg_reg_offset *range_node,
+		size_t max_offset)
+{
+	u32 length = 0;
+
+	if (range_node->start == 0 && range_node->end == 0) {
+		length = max_offset;
+	} else if (range_node->start < max_offset) {
+		if (range_node->end > max_offset)
+			length = max_offset - range_node->start;
+		else if (range_node->start < range_node->end)
+			length = range_node->end - range_node->start;
+	}
+
+	return length;
+}
+
+static int _dpu_dump_reg_range_cmp(void *priv, struct list_head *a,
+		struct list_head *b)
+{
+	struct dpu_dbg_reg_range *ar, *br;
+
+	if (!a || !b)
+		return 0;
+
+	ar = container_of(a, struct dpu_dbg_reg_range, head);
+	br = container_of(b, struct dpu_dbg_reg_range, head);
+
+	return ar->offset.start - br->offset.start;
+}
+
+/**
+ * _dpu_dump_reg_by_ranges - dump ranges or full range of the register blk base
+ * @dbg: register blk base structure
+ * @reg_dump_flag: dump target, memory, kernel log, or both
+ */
+static void _dpu_dump_reg_by_ranges(struct dpu_dbg_base *dbg_base,
+		struct dpu_dbg_reg_base *dbg,
+		u32 reg_dump_flag)
+{
+	char *addr;
+	size_t len;
+	struct dpu_dbg_reg_range *range_node;
+
+	if (!dbg || !(dbg->base || dbg->cb)) {
+		pr_err("dbg base is null!\n");
+		return;
+	}
+
+	dev_info(dbg_base->dev, "%s:=========%s DUMP=========\n", __func__,
+			dbg->name);
+
+	if (reg_dump_flag & DPU_DBG_DUMP_IN_COREDUMP)
+		drm_printf(dbg_base->dpu_dbg_printer,
+				"%s:=========%s DUMP=========\n",
+				__func__, dbg->name);
+
+	if (dbg->cb) {
+		dbg->cb(dbg->cb_ptr);
+	/* If there is a list to dump the registers by ranges, use the ranges */
+	} else if (!list_empty(&dbg->sub_range_list)) {
+		/* sort the list by start address first */
+		list_sort(NULL, &dbg->sub_range_list, _dpu_dump_reg_range_cmp);
+		list_for_each_entry(range_node, &dbg->sub_range_list, head) {
+			len = _dpu_dbg_get_dump_range(&range_node->offset,
+				dbg->max_offset);
+			addr = dbg->base + range_node->offset.start;
+
+			pr_debug("%s: range_base=0x%pK start=0x%x end=0x%x\n",
+				range_node->range_name,
+				addr, range_node->offset.start,
+				range_node->offset.end);
+
+			_dpu_dump_reg(dbg_base, range_node->range_name,
+					reg_dump_flag,
+					dbg->base, addr, len,
+					&range_node->reg_dump);
+		}
+	} else {
+		/* If there is no list to dump ranges, dump all registers */
+		dev_info(dbg_base->dev,
+				"Ranges not found, will dump full registers\n");
+		dev_info(dbg_base->dev, "base:0x%pK len:0x%zx\n", dbg->base,
+				dbg->max_offset);
+		addr = dbg->base;
+		len = dbg->max_offset;
+		_dpu_dump_reg(dbg_base, dbg->name, reg_dump_flag,
+				dbg->base, addr, len,
+				&dbg->reg_dump);
+	}
+}
+
+/**
+ * _dpu_dump_reg_by_blk - dump a named register base region
+ * @blk_name: register blk name
+ */
+static void _dpu_dump_reg_by_blk(struct dpu_dbg_base *dbg_base,
+		const char *blk_name)
+{
+	struct dpu_dbg_reg_base *blk_base;
+
+	if (!dbg_base)
+		return;
+
+	list_for_each_entry(blk_base, &dbg_base->reg_base_list, reg_base_head) {
+		if (strlen(blk_base->name) &&
+			!strcmp(blk_base->name, blk_name)) {
+			_dpu_dump_reg_by_ranges(dbg_base, blk_base,
+				dbg_base->enable_reg_dump);
+			break;
+		}
+	}
+}
+
+/**
+ * _dpu_dump_reg_all - dump all register regions
+ */
+static void _dpu_dump_reg_all(struct dpu_dbg_base *dbg_base)
+{
+	struct dpu_dbg_reg_base *blk_base;
+
+	if (!dbg_base)
+		return;
+
+	list_for_each_entry(blk_base, &dbg_base->reg_base_list, reg_base_head) {
+
+		if (!strlen(blk_base->name))
+			continue;
+
+		_dpu_dump_reg_by_blk(dbg_base, blk_base->name);
+	}
+}
+
+struct dpu_dbg_reg_base *_dpu_dump_get_blk_addr(struct dpu_dbg_base *dbg_base,
+		const char *blk_name)
+{
+	struct dpu_dbg_reg_base *blk_base;
+
+	list_for_each_entry(blk_base, &dbg_base->reg_base_list, reg_base_head)
+		if (strlen(blk_base->name) && !strcmp(blk_base->name, blk_name))
+			return blk_base;
+
+	return NULL;
+}
+
+void _dpu_dump_array(struct dpu_dbg_base *dbg_base,
+		struct dpu_dbg_reg_base *blk_arr[],
+		u32 len, bool do_panic, const char *name, bool dump_all)
+{
+	int i;
+
+	mutex_lock(&dbg_base->mutex);
+
+	if (dump_all || !blk_arr || !len) {
+		_dpu_dump_reg_all(dbg_base);
+	} else {
+		for (i = 0; i < len; i++) {
+			if (blk_arr[i] != NULL)
+				_dpu_dump_reg_by_ranges(dbg_base,
+						blk_arr[i],
+						dbg_base->enable_reg_dump);
+		}
+	}
+
+	if (do_panic)
+		panic(name);
+
+	mutex_unlock(&dbg_base->mutex);
+}
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h
index 3544af1a45c5..b2ab22be4c55 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h
@@ -1,5 +1,5 @@ 
 /* SPDX-License-Identifier: GPL-2.0-only */
-/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2015-2018, 2020 The Linux Foundation. All rights reserved.
  */
 
 #ifndef _DPU_HW_CATALOG_H
@@ -52,6 +52,8 @@ 
 
 
 #define DPU_HW_BLK_NAME_LEN	16
+/* default size of valid register space for MDSS_HW block (offset 0) */
+#define DEFAULT_MDSS_HW_BLOCK_SIZE 0x5C
 
 #define MAX_IMG_WIDTH 0x3fff
 #define MAX_IMG_HEIGHT 0x3fff
diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
index 49685571dc0e..f6fb0187388f 100644
--- a/drivers/gpu/drm/msm/msm_drv.c
+++ b/drivers/gpu/drm/msm/msm_drv.c
@@ -1,6 +1,6 @@ 
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2016-2018, 2020 The Linux Foundation. All rights reserved.
  * Copyright (C) 2013 Red Hat
  * Author: Rob Clark <robdclark@gmail.com>
  */
@@ -17,6 +17,7 @@ 
 #include <drm/drm_prime.h>
 #include <drm/drm_of.h>
 #include <drm/drm_vblank.h>
+#include "dpu_dbg.h"
 
 #include "msm_drv.h"
 #include "msm_debugfs.h"
@@ -268,6 +269,8 @@  static int msm_drm_uninit(struct device *dev)
 		msm_fbdev_free(ddev);
 #endif
 
+	dpu_dbg_destroy();
+
 	drm_mode_config_cleanup(ddev);
 
 	pm_runtime_get_sync(dev);
@@ -1303,6 +1306,7 @@  static int msm_pdev_probe(struct platform_device *pdev)
 
 fail:
 	of_platform_depopulate(&pdev->dev);
+	dpu_dbg_destroy();
 	return ret;
 }