mirror of
https://github.com/linux-sunxi/meta-sunxi.git
synced 2024-11-16 02:08:23 +01:00
1553 lines
41 KiB
Diff
1553 lines
41 KiB
Diff
|
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
|
||
|
|