meta-sunxi/recipes-kernel/linux/linux-mainline-6.1.9/orange-pi-zero2/0006-drv-add-dump_reg-and-sunxi-sysinfo-drivers.patch

1553 lines
41 KiB
Diff
Raw Normal View History

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