From ad291f3fffe361a3a424f9a892475b6343870ed9 Mon Sep 17 00:00:00 2001
From: afaulkner420 <afaulkner420@gmail.com>
Date: Fri, 25 Mar 2022 19:28:00 +0000
Subject: [PATCH 03/11] Add dump_reg and sunxi-sysinfo drivers

---
 drivers/char/Kconfig                       |   2 +
 drivers/char/Makefile                      |   2 +
 drivers/char/dump_reg/Kconfig              |  21 +
 drivers/char/dump_reg/Makefile             |   2 +
 drivers/char/dump_reg/dump_reg.c           | 888 +++++++++++++++++++++
 drivers/char/dump_reg/dump_reg.h           | 132 +++
 drivers/char/dump_reg/dump_reg_misc.c      | 209 +++++
 drivers/char/sunxi-sysinfo/Kconfig         |  10 +
 drivers/char/sunxi-sysinfo/Makefile        |   5 +
 drivers/char/sunxi-sysinfo/sunxi-sysinfo.c | 178 +++++
 10 files changed, 1449 insertions(+)
 create mode 100644 drivers/char/dump_reg/Kconfig
 create mode 100644 drivers/char/dump_reg/Makefile
 create mode 100644 drivers/char/dump_reg/dump_reg.c
 create mode 100644 drivers/char/dump_reg/dump_reg.h
 create mode 100644 drivers/char/dump_reg/dump_reg_misc.c
 create mode 100644 drivers/char/sunxi-sysinfo/Kconfig
 create mode 100644 drivers/char/sunxi-sysinfo/Makefile
 create mode 100644 drivers/char/sunxi-sysinfo/sunxi-sysinfo.c

diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index 740811893..e8d6b5cb1 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -131,6 +131,8 @@ config POWERNV_OP_PANEL
 	  If unsure, say M here to build it as a module called powernv-op-panel.
 
 source "drivers/char/ipmi/Kconfig"
+source "drivers/char/sunxi-sysinfo/Kconfig"
+source "drivers/char/dump_reg/Kconfig"
 
 config DS1620
 	tristate "NetWinder thermometer support"
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index 264eb398f..c638d4426 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_VIRTIO_CONSOLE)	+= virtio_console.o
 obj-$(CONFIG_MSPEC)		+= mspec.o
 obj-$(CONFIG_UV_MMTIMER)	+= uv_mmtimer.o
 obj-$(CONFIG_IBM_BSR)		+= bsr.o
+obj-$(CONFIG_ARCH_SUNXI)        += sunxi-sysinfo/
 
 obj-$(CONFIG_PRINTER)		+= lp.o
 
@@ -46,3 +47,4 @@ obj-$(CONFIG_PS3_FLASH)		+= ps3flash.o
 obj-$(CONFIG_XILLYBUS_CLASS)	+= xillybus/
 obj-$(CONFIG_POWERNV_OP_PANEL)	+= powernv-op-panel.o
 obj-$(CONFIG_ADI)		+= adi.o
+obj-$(CONFIG_DUMP_REG)          += dump_reg/
diff --git a/drivers/char/dump_reg/Kconfig b/drivers/char/dump_reg/Kconfig
new file mode 100644
index 000000000..dbf24c59f
--- /dev/null
+++ b/drivers/char/dump_reg/Kconfig
@@ -0,0 +1,21 @@
+#
+# dump reg config.
+#
+
+config DUMP_REG
+	tristate "dump reg driver for sunxi platform"
+	default y
+	help
+	  Say y here if you want to support dump regs module.
+	  The dump regs module is used to dump regs of any devices
+	  if you want it, When in doubt, say "Y".
+
+config DUMP_REG_MISC
+	tristate "dump reg misc driver"
+	depends on DUMP_REG
+	default y
+	help
+	  Add misc driver support, you can use dump regs function
+	  via ("/sys/class/...") sysfs interface.
+	  When in doubt, say "Y".
+
diff --git a/drivers/char/dump_reg/Makefile b/drivers/char/dump_reg/Makefile
new file mode 100644
index 000000000..e953f413b
--- /dev/null
+++ b/drivers/char/dump_reg/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_DUMP_REG) += dump_reg.o
+obj-$(CONFIG_DUMP_REG_MISC) += dump_reg_misc.o
diff --git a/drivers/char/dump_reg/dump_reg.c b/drivers/char/dump_reg/dump_reg.c
new file mode 100644
index 000000000..8c227b08d
--- /dev/null
+++ b/drivers/char/dump_reg/dump_reg.c
@@ -0,0 +1,888 @@
+/*
+ * dump registers sysfs driver
+ *
+ * Copyright(c) 2015-2018 Allwinnertech Co., Ltd.
+ *      http://www.allwinnertech.com
+ *
+ * Author: Liugang <liugang@allwinnertech.com>
+ *         Xiafeng <xiafeng@allwinnertech.com>
+ *         Martin <wuyan@allwinnertech.com>
+ *         Lewis  <liuyu@allwinnertech.com>
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kdev_t.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/miscdevice.h>
+#include <linux/seq_file.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
+#include <linux/mod_devicetable.h>
+#include "dump_reg.h"
+
+/* the register and vaule to be test by dump_reg */
+static u32 test_addr;
+static u32 test_size;
+static struct class *dump_class;
+
+/* Access in byte mode ? 1: byte-mode, 0: word-mode */
+static unsigned int rw_byte_mode;
+
+/* for dump_reg class */
+static struct dump_addr dump_para;
+static struct write_group *wt_group;
+static struct compare_group *cmp_group;
+
+static u32 _read(void __iomem *vaddr)
+{
+	if (rw_byte_mode)
+		return (u32)readb(vaddr);
+	else
+		return readl(vaddr);
+}
+
+static void _write(u32 val, void __iomem *vaddr)
+{
+	if (rw_byte_mode)
+		writeb((u8)val, vaddr);
+	else
+		writel(val, vaddr);
+}
+
+static void __iomem *_io_remap(unsigned long paddr, size_t size)
+{
+	return ioremap(paddr, size);
+}
+
+static void _io_unmap(void __iomem *vaddr)
+{
+	iounmap(vaddr);
+}
+
+static void __iomem *_mem_remap(unsigned long paddr, size_t size)
+{
+	return (void __iomem *)phys_to_virt(paddr);
+}
+
+/*
+ * Convert a physical address (which is already mapped) to virtual address
+ */
+static void __iomem *_get_vaddr(struct dump_addr *dump_addr, unsigned long uaddr)
+{
+	unsigned long offset = uaddr - dump_addr->uaddr_start;
+	return (void __iomem *)(dump_addr->vaddr_start + offset);
+}
+
+const struct dump_struct dump_table[] = {
+	{
+		.addr_start = SUNXI_IO_PHYS_START,
+		.addr_end   = SUNXI_IO_PHYS_END,
+		.remap = _io_remap,
+		.unmap = _io_unmap,
+		.get_vaddr = _get_vaddr,
+		.read  = _read,
+		.write = _write,
+	},
+	{
+		.addr_start = SUNXI_PLAT_PHYS_START,
+		.addr_end   = SUNXI_PLAT_PHYS_END,
+		.remap = _mem_remap,
+		.unmap = NULL,
+		.get_vaddr = _get_vaddr,
+		.read  = _read,
+		.write = _write,
+	},
+#if defined(SUNXI_IOMEM_START)
+	{
+		.addr_start = SUNXI_IOMEM_START,
+		.addr_end   = SUNXI_IOMEM_END,
+		.remap = NULL,  /* .remap = NULL: uaddr is a virtual address */
+		.unmap = NULL,
+		.get_vaddr = _get_vaddr,
+		.read  = _read,
+		.write = _write,
+	},
+#endif
+	{
+		.addr_start = SUNXI_MEM_PHYS_START,
+		.addr_end   = SUNXI_MEM_PHYS_END,
+		.remap = NULL,  /* .remap = NULL: uaddr is a virtual address */
+		.unmap = NULL,
+		.get_vaddr = _get_vaddr,
+		.read  = _read,
+		.write = _write,
+	},
+};
+EXPORT_SYMBOL(dump_table);
+
+/**
+ * __addr_valid - check if @uaddr is valid.
+ * @uaddr: addr to judge.
+ *
+ * return index if @addr is valid, -ENXIO if not.
+ */
+int __addr_valid(unsigned long uaddr)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(dump_table); i++)
+		if (uaddr >= dump_table[i].addr_start &&
+		    uaddr <= dump_table[i].addr_end)
+			return i;
+	return -ENXIO;
+}
+EXPORT_SYMBOL(__addr_valid);
+
+/**
+ * __dump_regs_ex - dump a range of registers' value, copy to buf.
+ * @dump_addr: start and end address of registers.
+ * @buf: store the dump info.
+ * @buf_size: buf size
+ *
+ * return bytes written to buf, <=0 indicate err
+ */
+ssize_t __dump_regs_ex(struct dump_addr *dump_addr, char *buf, ssize_t buf_size)
+{
+	int index;
+	ssize_t cnt = 0;
+	unsigned long uaddr;
+	unsigned long remap_size;
+	const struct dump_struct *dump;
+
+	/* Make the address 4-bytes aligned */
+	dump_addr->uaddr_start &= (~0x3UL);
+	dump_addr->uaddr_end &= (~0x3UL);
+	remap_size = dump_addr->uaddr_end - dump_addr->uaddr_start + 4;
+
+	index = __addr_valid(dump_addr->uaddr_start);
+	if ((index < 0) || (index != __addr_valid(dump_addr->uaddr_end)) ||
+	    (buf == NULL)) {
+		pr_err("%s(): Invalid para: index=%d, start=0x%lx, end=0x%lx, buf=0x%p\n",
+		       __func__, index, dump_addr->uaddr_start, dump_addr->uaddr_end, buf);
+		return -EIO;
+	}
+
+	dump = &dump_table[index];
+	if (dump->remap) {
+		dump_addr->vaddr_start = dump->remap(dump_addr->uaddr_start, remap_size);
+		if (!dump_addr->vaddr_start) {
+			pr_err("%s(): remap failed\n", __func__);
+			return -EIO;
+		}
+	} else  /* if (dump->remap = NULL), then treat uaddr as a virtual address */
+		dump_addr->vaddr_start = (void __iomem *)dump_addr->uaddr_start;
+
+	if (dump_addr->uaddr_start == dump_addr->uaddr_end) {
+		cnt = sprintf(buf, "0x%08x\n", dump->read(dump_addr->vaddr_start));
+		goto out;
+	}
+
+	for (uaddr = (dump_addr->uaddr_start & ~0x0F); uaddr <= dump_addr->uaddr_end;
+	     uaddr += 4) {
+		if (!(uaddr & 0x0F))
+			cnt += snprintf(buf + cnt, buf_size - cnt,
+				     "\n" PRINT_ADDR_FMT ":", uaddr);
+
+		if (cnt >= buf_size) {
+			pr_warn("Range too large, strings buffer overflow\n");
+			cnt = buf_size;
+			goto out;
+		}
+
+		if (uaddr < dump_addr->uaddr_start)  /* Don't show unused uaddr */
+			/* "0x12345678 ", 11 space */
+			cnt += snprintf(buf + cnt, buf_size - cnt, "           ");
+		else
+			cnt += snprintf(buf + cnt, buf_size - cnt, " 0x%08x",
+				dump->read(dump->get_vaddr(dump_addr, uaddr)));
+	}
+	cnt += snprintf(buf + cnt, buf_size - cnt, "\n");
+
+	pr_debug("%s(): start=0x%lx, end=0x%lx, return=%zd\n", __func__,
+		 dump_addr->uaddr_start, dump_addr->uaddr_end, cnt);
+
+out:
+	if (dump->unmap)
+		dump->unmap(dump_addr->vaddr_start);
+
+	return cnt;
+}
+EXPORT_SYMBOL(__dump_regs_ex);
+
+/**
+ * __parse_dump_str - parse the input string for dump attri.
+ * @buf: the input string, eg: "0x01c20000,0x01c20300".
+ * @size: buf size.
+ * @start: store the start reg's addr parsed from buf, eg 0x01c20000.
+ * @end: store the end reg's addr parsed from buf, eg 0x01c20300.
+ *
+ * return 0 if success, otherwise failed.
+ */
+int __parse_dump_str(const char *buf, size_t size,
+			    unsigned long *start, unsigned long *end)
+{
+	char *ptr = NULL;
+	char *ptr2 = (char *)buf;
+	int ret = 0, times = 0;
+
+	/* Support single address mode, some time it haven't ',' */
+next:
+	/*
+	 * Default dump only one register(*start =*end).
+	 * If ptr is not NULL, we will cover the default value of end.
+	 */
+	if (times == 1)
+		*start = *end;
+
+	if (!ptr2 || (ptr2 - buf) >= size)
+		goto out;
+
+	ptr = ptr2;
+	ptr2 = strnchr(ptr, size - (ptr - buf), ',');
+	if (ptr2) {
+		*ptr2 = '\0';
+		ptr2++;
+	}
+
+	ptr = strim(ptr);
+	if (!strlen(ptr))
+		goto next;
+
+	ret = kstrtoul(ptr, 16, end);
+	if (!ret) {
+		times++;
+		goto next;
+	} else
+		pr_warn("String syntax errors: \"%s\"\n", ptr);
+
+out:
+	return ret;
+}
+EXPORT_SYMBOL(__parse_dump_str);
+
+/**
+ * __write_show - dump a register's value, copy to buf.
+ * @pgroup: the addresses to read.
+ * @buf: store the dump info.
+ *
+ * return bytes written to buf, <=0 indicate err.
+ */
+ssize_t __write_show(struct write_group *pgroup, char *buf, ssize_t len)
+{
+#define WR_DATA_FMT PRINT_ADDR_FMT"  0x%08x  %s"
+
+	int i = 0;
+	ssize_t cnt = 0;
+	unsigned long reg = 0;
+	u32 val;
+	u8 rval_buf[16];
+	struct dump_addr dump_addr;
+
+	if (!pgroup) {
+		pr_err("%s,%d err, pgroup is NULL!\n", __func__, __LINE__);
+		goto end;
+	}
+
+	cnt += snprintf(buf, len - cnt, WR_PRINT_FMT);
+	if (cnt > len) {
+		cnt = -EINVAL;
+		goto end;
+	}
+
+	for (i = 0; i < pgroup->num; i++) {
+		reg = pgroup->pitem[i].reg_addr;
+		val = pgroup->pitem[i].val;
+		dump_addr.uaddr_start = reg;
+		dump_addr.uaddr_end = reg;
+		if (__dump_regs_ex(&dump_addr, rval_buf, sizeof(rval_buf)) < 0)
+			return -EINVAL;
+
+		cnt +=
+		    snprintf(buf + cnt, len - cnt, WR_DATA_FMT, reg, val,
+			     rval_buf);
+		if (cnt > len) {
+			cnt = len;
+			goto end;
+		}
+	}
+
+end:
+	return cnt;
+}
+EXPORT_SYMBOL(__write_show);
+
+/**
+ * __parse_write_str - parse the input string for write attri.
+ * @str: string to be parsed, eg: "0x01c20818 0x55555555".
+ * @reg_addr: store the reg address. eg: 0x01c20818.
+ * @val: store the expect value. eg: 0x55555555.
+ *
+ * return 0 if success, otherwise failed.
+ */
+static int __parse_write_str(char *str, unsigned long *reg_addr, u32 *val)
+{
+	char *ptr = str;
+	char *tstr = NULL;
+	int ret = 0;
+
+	/*
+	 * Skip the leading whitespace, find the true split symbol.
+	 * And it must be 'address value'.
+	 */
+	tstr = strim(str);
+	ptr = strchr(tstr, ' ');
+	if (!ptr)
+		return -EINVAL;
+
+	/*
+	 * Replaced split symbol with a %NUL-terminator temporary.
+	 * Will be fixed at end.
+	 */
+	*ptr = '\0';
+	ret = kstrtoul(tstr, 16, reg_addr);
+	if (ret)
+		goto out;
+
+	ret = kstrtou32(skip_spaces(ptr + 1), 16, val);
+
+out:
+	return ret;
+}
+
+/**
+ * __write_item_init - init for write attri. parse input string,
+ *                     and construct write struct.
+ * @ppgroup: store the struct allocated, the struct contains items parsed from
+ *           input buf.
+ * @buf: input string, eg: "0x01c20800 0x00000031,0x01c20818 0x55555555,...".
+ * @size: buf size.
+ *
+ * return 0 if success, otherwise failed.
+ */
+int __write_item_init(struct write_group **ppgroup, const char *buf,
+			     size_t size)
+{
+	char *ptr, *ptr2;
+	unsigned long addr = 0;
+	u32 val;
+	struct write_group *pgroup;
+
+	/* alloc item buffer */
+	pgroup = kmalloc(sizeof(struct write_group), GFP_KERNEL);
+	if (!pgroup)
+		return -ENOMEM;
+
+	pgroup->pitem = kmalloc(sizeof(struct write_item) * MAX_WRITE_ITEM,
+				GFP_KERNEL);
+	if (!pgroup->pitem) {
+		kfree(pgroup);
+		return -ENOMEM;
+	}
+
+	pgroup->num = 0;
+	ptr = (char *)buf;
+	do {
+		ptr2 = strchr(ptr, ',');
+		if (ptr2)
+			*ptr2 = '\0';
+
+		if (!__parse_write_str(ptr, &addr, &val)) {
+			pgroup->pitem[pgroup->num].reg_addr = addr;
+			pgroup->pitem[pgroup->num].val = val;
+			pgroup->num++;
+		} else
+			pr_err("%s: Failed to parse string: %s\n", __func__,
+			       ptr);
+
+		if (!ptr2)
+			break;
+
+		ptr = ptr2 + 1;
+		*ptr2 = ',';
+
+	} while (pgroup->num <= MAX_WRITE_ITEM);
+
+	/* free buffer if no valid item */
+	if (pgroup->num == 0) {
+		kfree(pgroup->pitem);
+		kfree(pgroup);
+		return -EINVAL;
+	}
+
+	*ppgroup = pgroup;
+	return 0;
+}
+EXPORT_SYMBOL(__write_item_init);
+
+/**
+ * __write_item_deinit - reled_addrse memory that cred_addrted by
+ *                       __write_item_init.
+ * @pgroup: the write struct allocated in __write_item_init.
+ */
+void __write_item_deinit(struct write_group *pgroup)
+{
+	if (pgroup != NULL) {
+		if (pgroup->pitem != NULL)
+			kfree(pgroup->pitem);
+		kfree(pgroup);
+	}
+}
+EXPORT_SYMBOL(__write_item_deinit);
+
+/**
+ * __compare_regs_ex - dump a range of registers' value, copy to buf.
+ * @pgroup: addresses of registers.
+ * @buf: store the dump info.
+ *
+ * return bytes written to buf, <= 0 indicate err.
+ */
+ssize_t __compare_regs_ex(struct compare_group *pgroup, char *buf,
+				 ssize_t len)
+{
+#define CMP_DATAO_FMT PRINT_ADDR_FMT"  0x%08x  0x%08x  0x%08x  OK\n"
+#define CMP_DATAE_FMT PRINT_ADDR_FMT"  0x%08x  0x%08x  0x%08x  ERR\n"
+
+	int i;
+	ssize_t cnt = 0;
+	unsigned long reg;
+	u32 expect, actual, mask;
+	u8 actualb[16];
+	struct dump_addr dump_addr;
+
+	if (!pgroup) {
+		pr_err("%s,%d err, pgroup is NULL!\n", __func__, __LINE__);
+		goto end;
+	}
+
+	cnt += snprintf(buf, len - cnt, CMP_PRINT_FMT);
+	if (cnt > len) {
+		cnt = -EINVAL;
+		goto end;
+	}
+
+	for (i = 0; i < pgroup->num; i++) {
+		reg = pgroup->pitem[i].reg_addr;
+		expect = pgroup->pitem[i].val_expect;
+		dump_addr.uaddr_start = reg;
+		dump_addr.uaddr_end = reg;
+		if (__dump_regs_ex(&dump_addr, actualb, sizeof(actualb)) < 0)
+			return -EINVAL;
+
+		if (kstrtou32(actualb, 16, &actual))
+			return -EINVAL;
+
+		mask = pgroup->pitem[i].val_mask;
+		if ((actual & mask) == (expect & mask))
+			cnt +=
+			    snprintf(buf + cnt, len - cnt, CMP_DATAO_FMT, reg,
+				     expect, actual, mask);
+		else
+			cnt +=
+			    snprintf(buf + cnt, len - cnt, CMP_DATAE_FMT, reg,
+				     expect, actual, mask);
+		if (cnt > len) {
+			cnt = -EINVAL;
+			goto end;
+		}
+	}
+
+end:
+	return cnt;
+}
+EXPORT_SYMBOL(__compare_regs_ex);
+
+/**
+ * __parse_compare_str - parse the input string for compare attri.
+ * @str: string to be parsed, eg: "0x01c20000 0x80000011 0x00000011".
+ * @reg_addr: store the reg address. eg: 0x01c20000.
+ * @val_expect: store the expect value. eg: 0x80000011.
+ * @val_mask: store the mask value. eg: 0x00000011.
+ *
+ * return 0 if success, otherwise failed.
+ */
+static int __parse_compare_str(char *str, unsigned long *reg_addr,
+			       u32 *val_expect, u32 *val_mask)
+{
+	unsigned long result_addr[3] = { 0 };
+	char *ptr = str;
+	char *ptr2 = NULL;
+	int i, ret = 0;
+
+	for (i = 0; i < ARRAY_SIZE(result_addr); i++) {
+		ptr = skip_spaces(ptr);
+		ptr2 = strchr(ptr, ' ');
+		if (ptr2)
+			*ptr2 = '\0';
+
+		ret = kstrtoul(ptr, 16, &result_addr[i]);
+		if (!ptr2)
+			break;
+
+		*ptr2 = ' ';
+
+		if (ret)
+			break;
+
+		ptr = ptr2 + 1;
+	}
+
+	*reg_addr = result_addr[0];
+	*val_expect = (u32) result_addr[1];
+	*val_mask = (u32) result_addr[2];
+
+	return ret;
+}
+
+/**
+ * __compare_item_init - init for compare attri. parse input string,
+ *                       and construct compare struct.
+ * @ppgroup: store the struct allocated, the struct contains items parsed from
+ *           input buf.
+ * @buf: input string,
+ *  eg: "0x01c20000 0x80000011 0x00000011,0x01c20004 0x0000c0a4 0x0000c0a0,...".
+ * @size: buf size.
+ *
+ * return 0 if success, otherwise failed.
+ */
+int __compare_item_init(struct compare_group **ppgroup,
+			       const char *buf, size_t size)
+{
+	char *ptr, *ptr2;
+	unsigned long addr = 0;
+	u32 val_expect = 0, val_mask = 0;
+	struct compare_group *pgroup = NULL;
+
+	/* alloc item buffer */
+	pgroup = kmalloc(sizeof(struct compare_group), GFP_KERNEL);
+	if (pgroup == NULL)
+		return -EINVAL;
+
+	pgroup->pitem = kmalloc(sizeof(struct compare_item) * MAX_COMPARE_ITEM,
+				GFP_KERNEL);
+	if (pgroup->pitem == NULL) {
+		kfree(pgroup);
+		return -EINVAL;
+	}
+
+	pgroup->num = 0;
+
+	/* get item from buf */
+	ptr = (char *)buf;
+	do {
+		ptr2 = strchr(ptr, ',');
+		if (ptr2)
+			*ptr2 = '\0';
+
+		if (!__parse_compare_str(ptr, &addr, &val_expect, &val_mask)) {
+			pgroup->pitem[pgroup->num].reg_addr = addr;
+			pgroup->pitem[pgroup->num].val_expect = val_expect;
+			pgroup->pitem[pgroup->num].val_mask = val_mask;
+			pgroup->num++;
+		} else
+			pr_err("%s: Failed to parse string: %s\n", __func__,
+			       ptr);
+
+		if (!ptr2)
+			break;
+
+		*ptr2 = ',';
+		ptr = ptr2 + 1;
+
+	} while (pgroup->num <= MAX_COMPARE_ITEM);
+
+	/* free buffer if no valid item */
+	if (pgroup->num == 0) {
+		kfree(pgroup->pitem);
+		kfree(pgroup);
+		return -EINVAL;
+	}
+	*ppgroup = pgroup;
+
+	return 0;
+}
+EXPORT_SYMBOL(__compare_item_init);
+
+/**
+ * __compare_item_deinit - reled_addrse memory that cred_addrted by
+ *                         __compare_item_init.
+ * @pgroup: the compare struct allocated in __compare_item_init.
+ */
+void __compare_item_deinit(struct compare_group *pgroup)
+{
+	if (pgroup) {
+		kfree(pgroup->pitem);
+		kfree(pgroup);
+	}
+}
+EXPORT_SYMBOL(__compare_item_deinit);
+
+/**
+ * dump_show - show func of dump attribute.
+ * @dev: class ptr.
+ * @attr: attribute ptr.
+ * @buf: the input buf which contain the start and end reg.
+ *       eg: "0x01c20000,0x01c20100\n".
+ *
+ * return size written to the buf, otherwise failed.
+ */
+static ssize_t
+dump_show(struct class *class, struct class_attribute *attr, char *buf)
+{
+	return __dump_regs_ex(&dump_para, buf, PAGE_SIZE);
+}
+
+static ssize_t
+dump_store(struct class *class, struct class_attribute *attr,
+	   const char *buf, size_t count)
+{
+	int index;
+	unsigned long start_reg = 0;
+	unsigned long end_reg = 0;
+
+	if (__parse_dump_str(buf, count, &start_reg, &end_reg)) {
+		pr_err("%s,%d err, invalid para!\n", __func__, __LINE__);
+		goto err;
+	}
+
+	index = __addr_valid(start_reg);
+	if ((index < 0) || (index != __addr_valid(end_reg))) {
+		pr_err("%s,%d err, invalid para!\n", __func__, __LINE__);
+		goto err;
+	}
+
+	dump_para.uaddr_start = start_reg;
+	dump_para.uaddr_end = end_reg;
+	pr_debug("%s,%d, start_reg:" PRINT_ADDR_FMT ", end_reg:" PRINT_ADDR_FMT
+		 "\n", __func__, __LINE__, start_reg, end_reg);
+
+	return count;
+
+err:
+	dump_para.uaddr_start = 0;
+	dump_para.uaddr_end = 0;
+
+	return -EINVAL;
+}
+
+static ssize_t
+write_show(struct class *class, struct class_attribute *attr, char *buf)
+{
+	/* display write result */
+	return __write_show(wt_group, buf, PAGE_SIZE);
+}
+
+static ssize_t
+write_store(struct class *class, struct class_attribute *attr,
+	    const char *buf, size_t count)
+{
+	int i;
+	int index;
+	unsigned long reg;
+	u32 val;
+	const struct dump_struct *dump;
+	struct dump_addr dump_addr;
+
+	/* free if not NULL */
+	if (wt_group) {
+		__write_item_deinit(wt_group);
+		wt_group = NULL;
+	}
+
+	/* parse input buf for items that will be dumped */
+	if (__write_item_init(&wt_group, buf, count) < 0)
+		return -EINVAL;
+
+	/**
+	 * write reg
+	 * it is better if the regs been remaped and unmaped only once,
+	 * but we map everytime for the range between min and max address
+	 * maybe too large.
+	 */
+	for (i = 0; i < wt_group->num; i++) {
+		reg = wt_group->pitem[i].reg_addr;
+		dump_addr.uaddr_start = reg;
+		val = wt_group->pitem[i].val;
+		index = __addr_valid(reg);
+		dump = &dump_table[index];
+		if (dump->remap)
+			dump_addr.vaddr_start = dump->remap(reg, 4);
+		else
+			dump_addr.vaddr_start = (void __iomem *)reg;
+		dump->write(val, dump->get_vaddr(&dump_addr, reg));
+		if (dump->unmap)
+			dump->unmap(dump_addr.vaddr_start);
+	}
+
+	return count;
+}
+
+static ssize_t
+compare_show(struct class *class, struct class_attribute *attr, char *buf)
+{
+	/* dump the items */
+	return __compare_regs_ex(cmp_group, buf, PAGE_SIZE);
+}
+
+static ssize_t
+compare_store(struct class *class, struct class_attribute *attr,
+	      const char *buf, size_t count)
+{
+	/* free if struct not null */
+	if (cmp_group) {
+		__compare_item_deinit(cmp_group);
+		cmp_group = NULL;
+	}
+
+	/* parse input buf for items that will be dumped */
+	if (__compare_item_init(&cmp_group, buf, count) < 0)
+		return -EINVAL;
+
+	return count;
+}
+
+static ssize_t
+rw_byte_show(struct class *class, struct class_attribute *attr, char *buf)
+{
+	return sprintf(buf, "read/write mode: %u(%s)\n", rw_byte_mode,
+		       rw_byte_mode ? "byte" : "word");
+}
+
+static ssize_t
+rw_byte_store(struct class *class, struct class_attribute *attr,
+	      const char *buf, size_t count)
+{
+	unsigned long value;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &value);
+	if (!ret && (value > 1)) {
+		pr_err("%s,%d err, invalid para!\n", __func__, __LINE__);
+		goto out;
+	}
+	rw_byte_mode = value;
+out:
+	return count;
+}
+
+static ssize_t
+test_show(struct class *class, struct class_attribute *attr, char *buf)
+{
+	return sprintf(buf, "addr:0x%08x\nsize:0x%08x\n", test_addr, test_size);
+}
+
+static ssize_t
+help_show(struct class *class, struct class_attribute *attr, char *buf)
+{
+	const char *info =
+		"dump single register:          echo {addr} > dump; cat dump\n"
+		"dump multi  registers:         echo {start-addr},{end-addr} > dump; cat dump\n"
+		"write single register:         echo {addr} {val} > write; cat write\n"
+		"write multi  registers:        echo {addr1} {val1},{addr2} {val2},... > write; cat write\n"
+		"compare single register:       echo {addr} {expect-val} {mask} > compare; cat compare\n"
+		"compare multi  registers:      echo {addr1} {expect-val1} {mask1},{addr2} {expect-val2} {mask2},... > compare; cat compare\n"
+		"byte-access mode:              echo 1 > rw_byte\n"
+		"word-access mode (default):    echo 0 > rw_byte\n"
+		"show test address info:        cat test\n";
+	return sprintf(buf, info);
+}
+
+static struct class_attribute dump_class_attrs[] = {
+	__ATTR(dump,     S_IWUSR | S_IRUGO, dump_show,     dump_store),
+	__ATTR(write,    S_IWUSR | S_IRUGO, write_show,    write_store),
+	__ATTR(compare,  S_IWUSR | S_IRUGO, compare_show,  compare_store),
+	__ATTR(rw_byte,  S_IWUSR | S_IRUGO, rw_byte_show,  rw_byte_store),
+	__ATTR(test,     S_IRUGO,           test_show, NULL),
+	__ATTR(help,     S_IRUGO,           help_show, NULL),
+};
+
+static const struct of_device_id sunxi_dump_reg_match[] = {
+	{.compatible = "allwinner,sunxi-dump-reg", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, sunxi_dump_reg_match);
+
+static int sunxi_dump_reg_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	struct device *dev = &pdev->dev;
+
+	int err;
+	int i;
+
+	/* sys/class/sunxi_dump */
+	dump_class = class_create(THIS_MODULE, "sunxi_dump");
+	if (IS_ERR(dump_class)) {
+		pr_err("%s:%u class_create() failed\n", __func__, __LINE__);
+		return PTR_ERR(dump_class);
+	}
+
+	/* sys/class/sunxi_dump/xxx */
+	for (i = 0; i < ARRAY_SIZE(dump_class_attrs); i++) {
+		err = class_create_file(dump_class, &dump_class_attrs[i]);
+		if (err) {
+			pr_err("%s:%u class_create_file() failed. err=%d\n", __func__, __LINE__, err);
+			while (i--) {
+				class_remove_file(dump_class, &dump_class_attrs[i]);
+			}
+			class_destroy(dump_class);
+			dump_class = NULL;
+			return err;
+		}
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev, "Fail to get IORESOURCE_MEM \n");
+		goto error;
+	}
+
+	test_addr = res->start;
+	test_size = resource_size(res);
+
+	return 0;
+error:
+	dev_err(dev, "sunxi_dump_reg probe error\n");
+	return -1;
+}
+
+static int sunxi_dump_reg_remove(struct platform_device *pdev)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(dump_class_attrs); i++) {
+		class_remove_file(dump_class, &dump_class_attrs[i]);
+	}
+
+	class_destroy(dump_class);
+	return 0;
+}
+
+static struct platform_driver sunxi_dump_reg_driver = {
+	.probe  = sunxi_dump_reg_probe,
+	.remove = sunxi_dump_reg_remove,
+	.driver = {
+		.name   = "dump_reg",
+		.owner  = THIS_MODULE,
+		.of_match_table = sunxi_dump_reg_match,
+	},
+};
+
+module_platform_driver(sunxi_dump_reg_driver);
+
+MODULE_ALIAS("dump reg driver");
+MODULE_ALIAS("platform:dump reg");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("1.0.3");
+MODULE_AUTHOR("xiafeng <xiafeng@allwinnertech.com>");
+MODULE_AUTHOR("Martin <wuyan@allwinnertech.com>");
+MODULE_AUTHOR("liuyu <SWCliuyus@allwinnertech.com>");
+MODULE_DESCRIPTION("dump registers driver");
diff --git a/drivers/char/dump_reg/dump_reg.h b/drivers/char/dump_reg/dump_reg.h
new file mode 100644
index 000000000..85af5c96e
--- /dev/null
+++ b/drivers/char/dump_reg/dump_reg.h
@@ -0,0 +1,132 @@
+/*
+ * dump registers head file
+ *
+ * (C) Copyright 2015-2018
+ * Reuuimlla Technology Co., Ltd. <www.reuuimllatech.com>
+ * Liugang <liugang@reuuimllatech.com>
+ * Xiafeng <xiafeng@allwinnertech.com>
+ * Martin  <wuyan@allwinnertech.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ */
+#ifndef _DUMP_REG_H_
+#define _DUMP_REG_H_
+
+/* BROM/SRAM/peripheral-registers space */
+#define SUNXI_IO_PHYS_START		(0x01000000UL)
+#define SUNXI_IO_PHYS_END		(SUNXI_IO_PHYS_START + SZ_128M + SZ_16M -1)
+
+/* DRAM space (Only map the first 1GB) */
+#define SUNXI_PLAT_PHYS_START		(0x40000000UL)
+#define SUNXI_PLAT_PHYS_END		(SUNXI_PLAT_PHYS_START + SZ_1G - 1)
+
+#if IS_ENABLED(CONFIG_ARM64)
+/* Virtual address space 1 */
+#define SUNXI_IOMEM_START		(0xffffff8000000000UL)
+#define SUNXI_IOMEM_END			(SUNXI_IOMEM_START + SZ_2G)
+/* Virtual address space 2 */
+#define SUNXI_MEM_PHYS_START		(0xffffffc000000000UL)
+#define SUNXI_MEM_PHYS_END		(SUNXI_MEM_PHYS_START + SZ_2G)
+/* Print format */
+#define PRINT_ADDR_FMT			"0x%016lx"
+#define CMP_PRINT_FMT                   "reg                 expect      actual      mask        result\n"
+#define WR_PRINT_FMT                    "reg                 to_write    after_write\n"
+#else
+/* Virtual address space 2 */
+#define SUNXI_MEM_PHYS_START		PAGE_OFFSET
+#define SUNXI_MEM_PHYS_END		(SUNXI_MEM_PHYS_START + SZ_1G - 1)
+/* Print format */
+#define PRINT_ADDR_FMT			"0x%08lx"
+#define CMP_PRINT_FMT                   "reg         expect      actual      mask        result\n"
+#define WR_PRINT_FMT                    "reg         to_write    after_write\n"
+#endif
+
+/* Item count */
+#define MAX_COMPARE_ITEM		64
+#define MAX_WRITE_ITEM			64
+
+struct dump_addr {
+	/* User specified address. Maybe physical or virtual address */
+	unsigned long uaddr_start;
+	unsigned long uaddr_end;
+	/* Virtual address */
+	void __iomem *vaddr_start;
+};
+
+struct dump_struct {
+	unsigned long addr_start;
+	unsigned long addr_end;
+	/* some registers' operate method maybe different */
+	void __iomem *(*remap)(unsigned long paddr, size_t size);
+	void (*unmap)(void __iomem *vaddr);
+	void __iomem *(*get_vaddr)(struct dump_addr *dump_addr, unsigned long uaddr);
+	 u32 (*read)(void __iomem *vaddr);
+	void (*write)(u32 val, void __iomem *vaddr);
+};
+
+/**
+ * compare_item - reg compare item struct
+ * @reg_addr:	reg address.
+ * @val_expect: expected value, provided by caller.
+ * @val_mask:   mask value, provided by caller. only mask bits will be compared.
+ */
+struct compare_item {
+	unsigned long reg_addr;
+	u32	val_expect;
+	u32	val_mask;
+};
+
+/**
+ * compare_group - reg compare group struct
+ * @num:	pitem element count. cannot exceed MAX_COMPARE_ITEM.
+ * @pitem:	items that will be compared, provided by caller.
+ */
+struct compare_group {
+	u32	num;
+	u32	reserve;
+	struct compare_item *pitem;
+};
+
+/**
+ * write_item - reg write item struct
+ * @reg_addr:	reg address.
+ * @val:	value to write
+ */
+struct write_item {
+	unsigned long reg_addr;
+	u32	val;
+	u32	reserve;
+};
+
+/**
+ * write_group - reg write group struct
+ * @num:	pitem element count. cannot exceed MAX_WRITE_ITEM.
+ * @pitem:	items that will be write, provided by caller.
+ */
+struct write_group {
+	u32	num;
+	u32	reserve;
+	struct write_item *pitem;
+};
+
+extern const struct dump_struct dump_table[4];
+
+int __addr_valid(unsigned long addr);
+ssize_t __dump_regs_ex(struct dump_addr *reg, char *buf, ssize_t len);
+int __parse_dump_str(const char *buf, size_t size,
+						unsigned long *start, unsigned long *end);
+ssize_t __write_show(struct write_group *pgroup, char *buf, ssize_t len);
+int  __write_item_init(struct write_group **ppgroup, const char *buf,
+						size_t size);
+void __write_item_deinit(struct write_group *pgroup);
+ssize_t __compare_regs_ex(struct compare_group *pgroup, char *buf,
+							ssize_t len);
+int  __compare_item_init(struct compare_group **ppgroup,
+						const char *buf, size_t size);
+void __compare_item_deinit(struct compare_group *pgroup);
+
+#endif /* _DUMP_REG_H_ */
diff --git a/drivers/char/dump_reg/dump_reg_misc.c b/drivers/char/dump_reg/dump_reg_misc.c
new file mode 100644
index 000000000..238ddd147
--- /dev/null
+++ b/drivers/char/dump_reg/dump_reg_misc.c
@@ -0,0 +1,209 @@
+/*
+ * misc dump registers driver -
+ * User space application could use dump-reg functions through file operations
+ * (open/read/write/close) to the sysfs node created by this driver.
+ *
+ * Copyright(c) 2015-2018 Allwinnertech Co., Ltd.
+ *      http://www.allwinnertech.com
+ *
+ * Author: Liugang <liugang@allwinnertech.com>
+ *         Xiafeng <xiafeng@allwinnertech.com>
+ *         Martin  <wuyan@allwinnertech.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kdev_t.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/miscdevice.h>
+#include <linux/seq_file.h>
+#include "dump_reg.h"
+
+/* for dump_reg misc driver */
+static struct dump_addr misc_dump_para;
+static struct write_group *misc_wt_group;
+static struct compare_group *misc_cmp_group;
+
+static ssize_t
+misc_dump_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	return __dump_regs_ex(&misc_dump_para, buf, PAGE_SIZE);
+}
+
+static ssize_t
+misc_dump_store(struct device *dev, struct device_attribute *attr,
+		const char *buf, size_t size)
+{
+	int index;
+	unsigned long start_reg = 0;
+	unsigned long end_reg = 0;
+
+	if (__parse_dump_str(buf, size, &start_reg, &end_reg)) {
+		pr_err("%s,%d err, invalid para!\n", __func__, __LINE__);
+		goto err;
+	}
+
+	index = __addr_valid(start_reg);
+	if ((index < 0) || (index != __addr_valid(end_reg))) {
+		pr_err("%s,%d err, invalid para!\n", __func__, __LINE__);
+		goto err;
+	}
+
+	misc_dump_para.uaddr_start = start_reg;
+	misc_dump_para.uaddr_end = end_reg;
+	pr_debug("%s,%d, start_reg:" PRINT_ADDR_FMT ", end_reg:" PRINT_ADDR_FMT
+		 "\n", __func__, __LINE__, start_reg, end_reg);
+
+	return size;
+
+err:
+	misc_dump_para.uaddr_start = 0;
+	misc_dump_para.uaddr_end = 0;
+
+	return -EINVAL;
+}
+
+static ssize_t
+misc_write_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	/* display write result */
+	return __write_show(misc_wt_group, buf, PAGE_SIZE);
+}
+
+static ssize_t
+misc_write_store(struct device *dev, struct device_attribute *attr,
+		 const char *buf, size_t size)
+{
+	int i;
+	int index;
+	unsigned long reg;
+	u32 val;
+	const struct dump_struct *dump;
+	struct dump_addr dump_addr;
+
+	/* free if not NULL */
+	if (misc_wt_group) {
+		__write_item_deinit(misc_wt_group);
+		misc_wt_group = NULL;
+	}
+
+	/* parse input buf for items that will be dumped */
+	if (__write_item_init(&misc_wt_group, buf, size) < 0)
+		return -EINVAL;
+
+	/**
+	 * write reg
+	 * it is better if the regs been remaped and unmaped only once,
+	 * but we map everytime for the range between min and max address
+	 * maybe too large.
+	 */
+	for (i = 0; i < misc_wt_group->num; i++) {
+		reg = misc_wt_group->pitem[i].reg_addr;
+		dump_addr.uaddr_start = reg;
+		val = misc_wt_group->pitem[i].val;
+		index = __addr_valid(reg);
+		dump = &dump_table[index];
+		if (dump->remap)
+			dump_addr.vaddr_start = dump->remap(reg, 4);
+		else
+			dump_addr.vaddr_start = (void __iomem *)reg;
+		dump->write(val, dump->get_vaddr(&dump_addr, reg));
+		if (dump->unmap)
+			dump->unmap(dump_addr.vaddr_start);
+	}
+
+	return size;
+}
+
+static ssize_t
+misc_compare_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	/* dump the items */
+	return __compare_regs_ex(misc_cmp_group, buf, PAGE_SIZE);
+}
+
+static ssize_t
+misc_compare_store(struct device *dev, struct device_attribute *attr,
+		   const char *buf, size_t size)
+{
+	/* free if struct not null */
+	if (misc_cmp_group) {
+		__compare_item_deinit(misc_cmp_group);
+		misc_cmp_group = NULL;
+	}
+
+	/* parse input buf for items that will be dumped */
+	if (__compare_item_init(&misc_cmp_group, buf, size) < 0)
+		return -EINVAL;
+
+	return size;
+}
+
+static DEVICE_ATTR(dump, S_IWUSR | S_IRUGO, misc_dump_show, misc_dump_store);
+static DEVICE_ATTR(write, S_IWUSR | S_IRUGO, misc_write_show, misc_write_store);
+static DEVICE_ATTR(compare, S_IWUSR | S_IRUGO, misc_compare_show,
+		   misc_compare_store);
+
+static struct attribute *misc_attributes[] = {  /* files under '/sys/devices/virtual/misc/sunxi-reg/rw/' */
+	&dev_attr_dump.attr,
+	&dev_attr_write.attr,
+	&dev_attr_compare.attr,
+	NULL,
+};
+
+static struct attribute_group misc_attribute_group = {
+	.name = "rw",  /* directory: '/sys/devices/virtual/misc/sunxi-reg/rw/' */
+	.attrs = misc_attributes,
+};
+
+static struct miscdevice dump_reg_dev = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "sunxi-reg",  /* device node: '/dev/sunxi-reg' */
+};
+
+static int __init misc_dump_reg_init(void)
+{
+	int err;
+
+	pr_info("misc dump reg init\n");
+	err = misc_register(&dump_reg_dev);
+	if (err) {
+		pr_err("dump register driver as misc device error!\n");
+		goto exit;
+	}
+
+	err = sysfs_create_group(&dump_reg_dev.this_device->kobj,
+				 &misc_attribute_group);
+	if (err)
+		pr_err("dump register sysfs create group failed!\n");
+
+exit:
+	return err;
+}
+
+static void __exit misc_dump_reg_exit(void)
+{
+	pr_info("misc dump reg exit\n");
+
+	sysfs_remove_group(&(dump_reg_dev.this_device->kobj),
+			   &misc_attribute_group);
+	misc_deregister(&dump_reg_dev);
+}
+
+module_init(misc_dump_reg_init);
+module_exit(misc_dump_reg_exit);
+
+MODULE_ALIAS("misc dump reg driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("1.0.1");
+MODULE_AUTHOR("xiafeng <xiafeng@allwinnertech.com>");
+MODULE_DESCRIPTION("misc dump registers driver");
diff --git a/drivers/char/sunxi-sysinfo/Kconfig b/drivers/char/sunxi-sysinfo/Kconfig
new file mode 100644
index 000000000..9b6e2f06d
--- /dev/null
+++ b/drivers/char/sunxi-sysinfo/Kconfig
@@ -0,0 +1,10 @@
+#
+# sunxi system information driver.
+#
+
+config SUNXI_SYS_INFO
+	tristate "sunxi system info driver"
+	default y
+	help
+	  This driver is used for query system information.
+	  If you don't know whether need it, please select y.
diff --git a/drivers/char/sunxi-sysinfo/Makefile b/drivers/char/sunxi-sysinfo/Makefile
new file mode 100644
index 000000000..188696592
--- /dev/null
+++ b/drivers/char/sunxi-sysinfo/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for sunxi system information driver
+#
+
+obj-$(CONFIG_SUNXI_SYS_INFO) += sunxi-sysinfo.o
diff --git a/drivers/char/sunxi-sysinfo/sunxi-sysinfo.c b/drivers/char/sunxi-sysinfo/sunxi-sysinfo.c
new file mode 100644
index 000000000..349b92bf1
--- /dev/null
+++ b/drivers/char/sunxi-sysinfo/sunxi-sysinfo.c
@@ -0,0 +1,178 @@
+/*
+ * Based on drivers/char/sunxi-sysinfo/sunxi-sysinfo.c
+ *
+ * Copyright (C) 2015 Allwinnertech Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/vmalloc.h>
+#include <linux/uaccess.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/compat.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/of.h>
+
+extern int sunxi_get_soc_chipid(unsigned char *chipid);
+extern int sunxi_get_serial(unsigned  char *serial);
+
+struct sunxi_info_quirks {
+	char * platform_name;
+};
+
+static const struct sunxi_info_quirks sun5i_h6_info_quirks = {
+	.platform_name  = "sun50i-h6",
+};
+
+static const struct sunxi_info_quirks sun5i_h616_info_quirks = {
+	.platform_name  = "sun50i-h616",
+};
+
+struct sunxi_info_quirks *quirks;
+
+static int soc_info_open(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+static int soc_info_release(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+static const struct file_operations soc_info_ops = {
+	.owner   = THIS_MODULE,
+	.open    = soc_info_open,
+	.release = soc_info_release,
+};
+
+struct miscdevice soc_info_device = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name  = "sunxi_soc_info",
+	.fops  = &soc_info_ops,
+};
+
+static ssize_t sys_info_show(struct class *class,
+			     struct class_attribute *attr, char *buf)
+{
+	int i;
+	int databuf[4] = {0};
+	char tmpbuf[129] = {0};
+	size_t size = 0;
+
+	/* platform */
+	size += sprintf(buf + size, "sunxi_platform    : %s\n", quirks->platform_name);
+
+	/* chipid */
+	sunxi_get_soc_chipid((u8 *)databuf);
+
+	for (i = 0; i < 4; i++)
+		sprintf(tmpbuf + i*8, "%08x", databuf[i]);
+	tmpbuf[128] = 0;
+	size += sprintf(buf + size, "sunxi_chipid      : %s\n", tmpbuf);
+
+	/* serial */
+	sunxi_get_serial((u8 *)databuf);
+	for (i = 0; i < 4; i++)
+		sprintf(tmpbuf + i*8, "%08x", databuf[i]);
+	tmpbuf[128] = 0;
+	size += sprintf(buf + size, "sunxi_serial      : %s\n", tmpbuf);
+
+	return size;
+}
+
+static struct class_attribute info_class_attrs[] = {
+	__ATTR(sys_info, 0644, sys_info_show, NULL),
+};
+
+static struct class info_class = {
+	.name           = "sunxi_info",
+	.owner          = THIS_MODULE,
+};
+
+static const struct of_device_id sunxi_info_match[] = {
+        {
+		.compatible = "allwinner,sun50i-h6-sys-info",
+		.data = &sun5i_h6_info_quirks,
+        },
+        {
+		.compatible = "allwinner,sun50i-h616-sys-info",
+		.data = &sun5i_h616_info_quirks,
+        },
+        {}
+};
+
+static int sunxi_info_probe(struct platform_device *pdev)
+{
+	int i, ret = 0;
+
+	quirks = of_device_get_match_data(&pdev->dev);
+	if (quirks == NULL) {
+		dev_err(&pdev->dev, "Failed to determine the quirks to use\n");
+		return -ENODEV;
+	}
+
+	ret = class_register(&info_class);
+	if (ret != 0)
+		return ret;
+
+	/* need some class specific sysfs attributes */
+	for (i = 0; i < ARRAY_SIZE(info_class_attrs); i++) {
+		ret = class_create_file(&info_class, &info_class_attrs[i]);
+		if (ret)
+			goto out_class_create_file_failed;
+	}
+
+	ret = misc_register(&soc_info_device);
+	if (ret != 0) {
+		pr_err("%s: misc_register() failed!(%d)\n", __func__, ret);
+		class_unregister(&info_class);
+		return ret;
+	}
+
+	return ret;
+
+out_class_create_file_failed:
+	class_unregister(&info_class);
+
+	return ret;
+}
+
+static int sunxi_info_remove(struct platform_device *pdev)
+{
+	misc_deregister(&soc_info_device);
+	class_unregister(&info_class);
+
+	return 0;
+}
+
+static struct platform_driver sunxi_info_driver = {
+        .probe  = sunxi_info_probe,
+        .remove = sunxi_info_remove,
+        .driver = {
+                .name   = "sunxi_info",
+                .owner  = THIS_MODULE,
+                .of_match_table = sunxi_info_match,
+        },
+};
+module_platform_driver(sunxi_info_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("xiafeng<xiafeng@allwinnertech.com>");
+MODULE_DESCRIPTION("sunxi sys info.");
-- 
2.25.1