From 8d2ca284c7221670320d9a1dc50d1c8588dae6e4 Mon Sep 17 00:00:00 2001 From: afaulkner420 Date: Fri, 25 Mar 2022 19:28:00 +0000 Subject: [PATCH 142/153] 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 30fe9848d..89c4180cf 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -132,6 +132,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 1b35d1724..f7b466d42 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 @@ -45,3 +46,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 + * Xiafeng + * Martin + * Lewis + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 "); +MODULE_AUTHOR("Martin "); +MODULE_AUTHOR("liuyu "); +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. + * Liugang + * Xiafeng + * Martin + * + * 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 + * Xiafeng + * Martin + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 "); +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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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"); +MODULE_DESCRIPTION("sunxi sys info."); -- 2.35.3