| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #include <linux/cdev.h> |
| #include <linux/types.h> |
| #include <linux/fs.h> |
| #include <linux/device.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/uaccess.h> |
| #include <linux/sched.h> |
| #include <linux/platform_device.h> |
| #include <linux/of.h> |
| #include <linux/of_fdt.h> |
| #include <linux/of_device.h> |
| #include <linux/irqreturn.h> |
| #include <linux/module.h> |
| #include <linux/mm.h> |
| |
| #include <linux/cpu.h> |
| #include <linux/smp.h> |
| #include <linux/kallsyms.h> |
| #include <linux/of_irq.h> |
| #include <linux/interrupt.h> |
| #include <linux/amlogic/page_trace.h> |
| #include <linux/amlogic/cpu_version.h> |
| #include <linux/arm-smccc.h> |
| #include <linux/highmem.h> |
| #include "dmc_monitor.h" |
| #include "ddr_port.h" |
| #include <linux/amlogic/gki_module.h> |
| |
| struct dmc_monitor *dmc_mon; |
| |
| static unsigned long init_dev_mask; |
| static unsigned long init_start_addr; |
| static unsigned long init_end_addr; |
| |
| static int early_dmc_param(char *buf) |
| { |
| unsigned long s_addr, e_addr, mask; |
| /* |
| * Patten: dmc_montiro=[start_addr],[end_addr],[mask] |
| * Example: dmc_monitor=0x00000000,0x20000000,0x7fce |
| */ |
| if (!buf) |
| return -EINVAL; |
| |
| if (sscanf(buf, "%lx,%lx,%lx", &s_addr, &e_addr, &mask) != 3) |
| return -EINVAL; |
| |
| init_start_addr = s_addr; |
| init_end_addr = e_addr; |
| init_dev_mask = mask; |
| |
| pr_info("%s, buf:%s, %lx-%lx, %lx\n", |
| __func__, buf, s_addr, e_addr, mask); |
| |
| return 0; |
| } |
| |
| __setup("dmc_monitor=", early_dmc_param); |
| |
| void show_violation_mem(unsigned long addr) |
| { |
| struct page *page; |
| unsigned long *p, *q; |
| |
| if (!pfn_valid(__phys_to_pfn(addr))) |
| return; |
| |
| page = phys_to_page(addr); |
| p = kmap_atomic(page); |
| if (!p) |
| return; |
| |
| q = p + ((addr & (PAGE_SIZE - 1)) / sizeof(*p)); |
| pr_emerg(DMC_TAG "[%08lx]:%016lx, f:%8lx, m:%p, a:%ps\n", |
| (unsigned long)q, *q, page->flags & 0xffffffff, |
| page->mapping, |
| (void *)get_page_trace(page)); |
| kunmap_atomic(p); |
| } |
| |
| unsigned long dmc_prot_rw(void __iomem *base, |
| unsigned long off, unsigned long value, int rw) |
| { |
| if (base) { |
| if (rw == DMC_WRITE) { |
| writel(value, base + off); |
| return 0; |
| } else { |
| return readl(base + off); |
| } |
| } else { |
| return dmc_rw(off + dmc_mon->io_base, value, rw); |
| } |
| } |
| |
| static inline int dual_dmc(struct dmc_monitor *mon) |
| { |
| return mon->configs & DUAL_DMC; |
| } |
| |
| static inline int quad_dmc(struct dmc_monitor *mon) |
| { |
| return mon->configs & QUAD_DMC; |
| } |
| |
| static int dev_name_to_id(const char *dev_name) |
| { |
| int i, len; |
| |
| for (i = 0; i < dmc_mon->port_num; i++) { |
| if (dmc_mon->port[i].port_id >= PORT_MAJOR && |
| !(dual_dmc(dmc_mon) || quad_dmc(dmc_mon))) |
| return -1; |
| len = strlen(dmc_mon->port[i].port_name); |
| if (!strncmp(dmc_mon->port[i].port_name, dev_name, len)) |
| break; |
| } |
| if (i >= dmc_mon->port_num) |
| return -1; |
| return dmc_mon->port[i].port_id; |
| } |
| |
| char *to_ports(int id) |
| { |
| int i; |
| |
| for (i = 0; i < dmc_mon->port_num; i++) { |
| if (dmc_mon->port[i].port_id == id) |
| return dmc_mon->port[i].port_name; |
| } |
| return NULL; |
| } |
| |
| char *to_sub_ports(int mid, int sid, char *id_str) |
| { |
| int i, s_port; |
| |
| if (dual_dmc(dmc_mon) || quad_dmc(dmc_mon)) /* not supported */ |
| return NULL; |
| |
| /* 7 is device port id */ |
| /* t5w 7 and 10 is device port id */ |
| if (strstr(dmc_mon->port[mid].port_name, "DEVICE")) { |
| if (mid == 7) |
| s_port = sid + PORT_MAJOR; |
| else if (mid == 10) |
| s_port = sid + PORT_MAJOR + 8; |
| |
| for (i = 0; i < dmc_mon->port_num; i++) { |
| if (dmc_mon->port[i].port_id == s_port) |
| return dmc_mon->port[i].port_name; |
| } |
| } |
| sprintf(id_str, "%2d", sid); |
| |
| return id_str; |
| } |
| |
| unsigned int get_all_dev_mask(void) |
| { |
| unsigned int ret = 0; |
| int i; |
| |
| if (dual_dmc(dmc_mon) || quad_dmc(dmc_mon)) /* not supported */ |
| return 0; |
| |
| for (i = 0; i < PORT_MAJOR; i++) { |
| if (dmc_mon->port[i].port_id >= PORT_MAJOR) |
| break; |
| ret |= (1 << dmc_mon->port[i].port_id); |
| } |
| return ret; |
| } |
| |
| static unsigned int get_other_dev_mask(void) |
| { |
| unsigned int ret = 0; |
| int i; |
| |
| if (dual_dmc(dmc_mon) || quad_dmc(dmc_mon)) /* not supported */ |
| return 0; |
| |
| for (i = 0; i < PORT_MAJOR; i++) { |
| if (dmc_mon->port[i].port_id >= PORT_MAJOR) |
| break; |
| |
| /* |
| * we don't want id with arm mali and device |
| * because these devices can access all ddr range |
| * and generate value-less report |
| */ |
| if (strstr(dmc_mon->port[i].port_name, "ARM") || |
| strstr(dmc_mon->port[i].port_name, "MALI") || |
| strstr(dmc_mon->port[i].port_name, "DEVICE")) |
| continue; |
| |
| ret |= (1 << dmc_mon->port[i].port_id); |
| } |
| return ret; |
| } |
| |
| size_t dump_dmc_reg(char *buf) |
| { |
| size_t sz = 0, i; |
| unsigned char dev; |
| u64 devices; |
| |
| if (dmc_mon->ops && dmc_mon->ops->dump_reg) |
| sz += dmc_mon->ops->dump_reg(buf); |
| sz += sprintf(buf + sz, "IO_BASE:%lx\n", dmc_mon->io_base); |
| sz += sprintf(buf + sz, "RANGE:%lx - %lx\n", |
| dmc_mon->addr_start, dmc_mon->addr_end); |
| sz += sprintf(buf + sz, "MONITOR DEVICE:\n"); |
| if (!dmc_mon->device) |
| return sz; |
| |
| if (dual_dmc(dmc_mon) || quad_dmc(dmc_mon)) { |
| devices = dmc_mon->device; |
| for (i = 0; i < sizeof(dmc_mon->device); i++) { |
| dev = devices & 0xff; |
| devices >>= 8ULL; |
| if (dev) |
| sz += sprintf(buf + sz, " %s\n", to_ports(dev)); |
| } |
| } else { |
| for (i = 0; i < sizeof(dmc_mon->device) * 8; i++) { |
| if (dmc_mon->device & (1ULL << i)) |
| sz += sprintf(buf + sz, " %s\n", to_ports(i)); |
| } |
| } |
| |
| return sz; |
| } |
| |
| static irqreturn_t dmc_monitor_irq_handler(int irq, void *dev_instance) |
| { |
| if (dmc_mon->ops && dmc_mon->ops->handle_irq) |
| dmc_mon->ops->handle_irq(dmc_mon, dev_instance); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static void clear_irq_work(struct work_struct *work) |
| { |
| /* |
| * DMC VIOLATION may happen very quickly and irq re-generated |
| * again before CPU leave IRQ mode, once this scenario happened, |
| * DMC protection would not generate IRQ again until we cleared |
| * it manually. |
| * Since no parameters used for irq handler, so we just call IRQ |
| * handler again to save code size. |
| */ |
| dmc_monitor_irq_handler(0, NULL); |
| schedule_delayed_work(&dmc_mon->work, HZ); |
| } |
| |
| static int dmc_regulation_dev(unsigned long dev, int add) |
| { |
| unsigned char *p, cur; |
| int i, set; |
| |
| if (dual_dmc(dmc_mon) || quad_dmc(dmc_mon)) { |
| /* dev is a set of 8 bit user id index */ |
| while (dev) { |
| cur = dev & 0xff; |
| set = 0; |
| p = (unsigned char *)&dmc_mon->device; |
| for (i = 0; i < sizeof(dmc_mon->device); i++) { |
| if (p[i] == cur) { /* already set */ |
| if (add) |
| set = 1; |
| else /* clear it */ |
| p[i] = 0; |
| break; |
| } |
| |
| if (p[i] == 0 && add) { /* find empty one */ |
| p[i] = (dev & 0xff); |
| set = 1; |
| break; |
| } |
| } |
| if (i == sizeof(dmc_mon->device) && !set && add) { |
| pr_err("%s, monitor device full\n", __func__); |
| return -EINVAL; |
| } |
| dev >>= 8; |
| } |
| } else { |
| if (add) /* dev is bit mask */ |
| dmc_mon->device |= dev; |
| else |
| dmc_mon->device &= ~(dev); |
| } |
| return 0; |
| } |
| |
| int dmc_set_monitor(unsigned long start, unsigned long end, |
| unsigned long dev_mask, int en) |
| { |
| if (!dmc_mon) |
| return -EINVAL; |
| |
| dmc_mon->addr_start = start; |
| dmc_mon->addr_end = end; |
| dmc_regulation_dev(dev_mask, en); |
| if (start < end && dmc_mon->ops && dmc_mon->ops->set_montor) |
| return dmc_mon->ops->set_montor(dmc_mon); |
| return -EINVAL; |
| } |
| EXPORT_SYMBOL(dmc_set_monitor); |
| |
| int dmc_set_monitor_by_name(unsigned long start, unsigned long end, |
| const char *port_name, int en) |
| { |
| long id; |
| |
| id = dev_name_to_id(port_name); |
| if (id >= 0 && (dual_dmc(dmc_mon) || quad_dmc(dmc_mon))) |
| return dmc_set_monitor(start, end, id, en); |
| else if (id < 0 || id >= BITS_PER_LONG) |
| return -EINVAL; |
| return dmc_set_monitor(start, end, 1UL << id, en); |
| } |
| EXPORT_SYMBOL(dmc_set_monitor_by_name); |
| |
| void dmc_monitor_disable(void) |
| { |
| if (dmc_mon->ops && dmc_mon->ops->disable) |
| return dmc_mon->ops->disable(dmc_mon); |
| } |
| EXPORT_SYMBOL(dmc_monitor_disable); |
| |
| static ssize_t range_show(struct class *cla, |
| struct class_attribute *attr, char *buf) |
| { |
| return sprintf(buf, "%08lx - %08lx\n", |
| dmc_mon->addr_start, dmc_mon->addr_end); |
| } |
| |
| static ssize_t range_store(struct class *cla, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| int ret; |
| unsigned long start, end; |
| |
| ret = sscanf(buf, "%lx %lx", &start, &end); |
| if (ret != 2) { |
| pr_info("%s, bad input:%s\n", __func__, buf); |
| return count; |
| } |
| dmc_set_monitor(start, end, dmc_mon->device, 1); |
| return count; |
| } |
| static CLASS_ATTR_RW(range); |
| |
| static ssize_t device_store(struct class *cla, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| int i; |
| |
| if (!strncmp(buf, "none", 4)) { |
| dmc_monitor_disable(); |
| return count; |
| } |
| |
| if (dual_dmc(dmc_mon) || quad_dmc(dmc_mon)) { |
| if (!strncmp(buf, "exclude", 3)) { |
| dmc_mon->configs &= ~POLICY_INCLUDE; |
| } else if (!strncmp(buf, "include", 3)) { |
| dmc_mon->configs |= POLICY_INCLUDE; |
| } else { |
| i = dev_name_to_id(buf); |
| if (i < 0) { |
| pr_info("bad device:%s\n", buf); |
| return -EINVAL; |
| } |
| if (dmc_regulation_dev(i, 1)) |
| return -EINVAL; |
| } |
| } else { |
| if (!strncmp(buf, "all", 3)) { |
| dmc_mon->device = get_all_dev_mask(); |
| } else if (!strncmp(buf, "other", 5)) { |
| dmc_mon->device = get_other_dev_mask(); |
| } else { |
| i = dev_name_to_id(buf); |
| if (i < 0) { |
| pr_info("bad device:%s\n", buf); |
| return -EINVAL; |
| } |
| dmc_regulation_dev(1 << i, 1); |
| } |
| } |
| if (dmc_mon->addr_start < dmc_mon->addr_end && dmc_mon->ops && |
| dmc_mon->ops->set_montor) |
| dmc_mon->ops->set_montor(dmc_mon); |
| |
| return count; |
| } |
| |
| static ssize_t device_show(struct class *cla, |
| struct class_attribute *attr, char *buf) |
| { |
| int i, s = 0; |
| |
| s += sprintf(buf + s, "supported device:\n"); |
| for (i = 0; i < dmc_mon->port_num; i++) { |
| if (dmc_mon->port[i].port_id >= PORT_MAJOR && |
| !(dual_dmc(dmc_mon) || quad_dmc(dmc_mon))) |
| break; |
| s += sprintf(buf + s, "%2d : %s\n", |
| dmc_mon->port[i].port_id, |
| dmc_mon->port[i].port_name); |
| } |
| return s; |
| } |
| static CLASS_ATTR_RW(device); |
| |
| static ssize_t dump_show(struct class *cla, |
| struct class_attribute *attr, char *buf) |
| { |
| return dump_dmc_reg(buf); |
| } |
| static CLASS_ATTR_RO(dump); |
| |
| static ssize_t debug_store(struct class *cla, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| long val = 0; |
| |
| if (kstrtoul(buf, 0, &val)) { |
| pr_info("invalid input:%s\n", buf); |
| return count; |
| } |
| |
| if (val <= 7) { |
| dmc_mon->debug = val; |
| if (dmc_mon->addr_start < dmc_mon->addr_end && dmc_mon->ops && |
| dmc_mon->ops->set_montor) |
| dmc_mon->ops->set_montor(dmc_mon); |
| |
| } else { |
| pr_err("Current parameters range from 0-7\n"); |
| } |
| |
| return count; |
| } |
| |
| static ssize_t debug_show(struct class *cla, |
| struct class_attribute *attr, char *buf) |
| { |
| int s = 0; |
| |
| s += sprintf(buf + s, "debug: 0x%02x\n", dmc_mon->debug); |
| if (dmc_mon->debug & DMC_DEBUG_WRITE) |
| s += sprintf(buf + s, "bit(0): write monitor enable\n"); |
| else |
| s += sprintf(buf + s, "bit(0): write monitor disable\n"); |
| |
| if (dmc_mon->debug & DMC_DEBUG_READ) |
| s += sprintf(buf + s, "bit(1): read monitor enable\n"); |
| else |
| s += sprintf(buf + s, "bit(1): read monitor disable\n"); |
| |
| if (dmc_mon->debug & DMC_DEBUG_CMA) |
| s += sprintf(buf + s, "bit(2): cma range not ignore\n"); |
| else |
| s += sprintf(buf + s, "bit(2): cma range ignore\n"); |
| |
| return s; |
| } |
| static CLASS_ATTR_RW(debug); |
| |
| static struct attribute *dmc_monitor_attrs[] = { |
| &class_attr_range.attr, |
| &class_attr_device.attr, |
| &class_attr_dump.attr, |
| &class_attr_debug.attr, |
| NULL |
| }; |
| ATTRIBUTE_GROUPS(dmc_monitor); |
| |
| static struct class dmc_monitor_class = { |
| .name = "dmc_monitor", |
| .class_groups = dmc_monitor_groups, |
| }; |
| |
| static void __init get_dmc_ops(int chip, struct dmc_monitor *mon) |
| { |
| /* set default parameters */ |
| mon->debug = 0x01; |
| |
| switch (chip) { |
| #ifdef CONFIG_AMLOGIC_DMC_MONITOR_G12 |
| case DMC_TYPE_G12A: |
| case DMC_TYPE_G12B: |
| case DMC_TYPE_SM1: |
| case DMC_TYPE_TL1: |
| mon->ops = &g12_dmc_mon_ops; |
| mon->mon_number = 1; |
| break; |
| #endif |
| #ifdef CONFIG_AMLOGIC_DMC_MONITOR_C1 |
| case DMC_TYPE_C1: |
| mon->ops = &c1_dmc_mon_ops; |
| mon->mon_number = 1; |
| break; |
| #endif |
| #ifdef CONFIG_AMLOGIC_DMC_MONITOR_GX |
| case DMC_TYPE_GXBB: |
| case DMC_TYPE_GXTVBB: |
| case DMC_TYPE_GXL: |
| case DMC_TYPE_GXM: |
| case DMC_TYPE_TXL: |
| case DMC_TYPE_TXLX: |
| case DMC_TYPE_AXG: |
| case DMC_TYPE_GXLX: |
| case DMC_TYPE_TXHD: |
| mon->ops = &gx_dmc_mon_ops; |
| mon->mon_number = 1; |
| break; |
| #endif |
| #ifdef CONFIG_AMLOGIC_DMC_MONITOR_TM2 |
| case DMC_TYPE_TM2: |
| if (is_meson_rev_b()) |
| mon->ops = &tm2_dmc_mon_ops; |
| else |
| #ifdef CONFIG_AMLOGIC_DMC_MONITOR_G12 |
| mon->ops = &g12_dmc_mon_ops; |
| #else |
| #error need support for revA |
| #endif |
| mon->mon_number = 1; |
| break; |
| |
| case DMC_TYPE_SC2: |
| mon->ops = &tm2_dmc_mon_ops; |
| break; |
| |
| case DMC_TYPE_T5: |
| case DMC_TYPE_T5D: |
| mon->ops = &tm2_dmc_mon_ops; |
| mon->mon_number = 1; |
| break; |
| #endif |
| #ifdef CONFIG_AMLOGIC_DMC_MONITOR_T7 |
| case DMC_TYPE_T7: |
| case DMC_TYPE_T3: |
| mon->ops = &t7_dmc_mon_ops; |
| mon->configs |= DUAL_DMC; |
| mon->configs |= POLICY_INCLUDE; |
| mon->mon_number = 2; |
| break; |
| case DMC_TYPE_P1: |
| mon->ops = &t7_dmc_mon_ops; |
| mon->configs |= POLICY_INCLUDE; |
| mon->configs |= QUAD_DMC; |
| mon->mon_number = 4; |
| break; |
| #endif |
| #ifdef CONFIG_AMLOGIC_DMC_MONITOR_S4 |
| case DMC_TYPE_S4: |
| case DMC_TYPE_T5W: |
| mon->ops = &s4_dmc_mon_ops; |
| mon->mon_number = 1; |
| break; |
| #endif |
| default: |
| pr_err("%s, Can't find ops for chip:%x\n", __func__, chip); |
| break; |
| } |
| } |
| |
| static int __init dmc_monitor_probe(struct platform_device *pdev) |
| { |
| int r = 0, irq, ports; |
| unsigned int tmp; |
| struct device_node *node; |
| struct ddr_port_desc *desc = NULL; |
| struct resource *res; |
| |
| pr_info("%s\n", __func__); |
| dmc_mon = devm_kzalloc(&pdev->dev, sizeof(*dmc_mon), GFP_KERNEL); |
| if (!dmc_mon) |
| return -ENOMEM; |
| |
| tmp = (unsigned long)of_device_get_match_data(&pdev->dev); |
| pr_info("%s, chip type:%d\n", __func__, tmp); |
| ports = ddr_find_port_desc(tmp, &desc); |
| if (ports < 0) { |
| pr_err("can't get port desc\n"); |
| dmc_mon = NULL; |
| return -EINVAL; |
| } |
| dmc_mon->chip = tmp; |
| dmc_mon->port_num = ports; |
| dmc_mon->port = desc; |
| get_dmc_ops(dmc_mon->chip, dmc_mon); |
| |
| node = pdev->dev.of_node; |
| r = of_property_read_u32(node, "reg_base", &tmp); |
| if (r < 0) { |
| pr_err("can't find iobase\n"); |
| dmc_mon = NULL; |
| return -EINVAL; |
| } |
| |
| dmc_mon->io_base = tmp; |
| |
| /* for register not in secure world */ |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (res) |
| dmc_mon->io_mem1 = ioremap(res->start, res->end - res->start); |
| if (dual_dmc(dmc_mon)) { |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
| if (res) |
| dmc_mon->io_mem2 = ioremap(res->start, |
| res->end - res->start); |
| } |
| |
| if (quad_dmc(dmc_mon)) { |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
| if (res) |
| dmc_mon->io_mem2 = ioremap(res->start, |
| res->end - res->start); |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 2); |
| if (res) |
| dmc_mon->io_mem3 = ioremap(res->start, |
| res->end - res->start); |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 3); |
| if (res) |
| dmc_mon->io_mem4 = ioremap(res->start, |
| res->end - res->start); |
| } |
| |
| irq = of_irq_get(node, 0); |
| if (dmc_mon->io_mem1) |
| r = request_irq(irq, dmc_monitor_irq_handler, |
| IRQF_SHARED, "dmc_monitor", dmc_mon->io_mem1); |
| else |
| r = request_irq(irq, dmc_monitor_irq_handler, |
| IRQF_SHARED, "dmc_monitor", dmc_mon); |
| if (r < 0) { |
| pr_err("request irq failed:%d, r:%d\n", irq, r); |
| dmc_mon = NULL; |
| return -EINVAL; |
| } |
| if (dual_dmc(dmc_mon)) { |
| irq = of_irq_get(node, 1); |
| r = request_irq(irq, dmc_monitor_irq_handler, |
| IRQF_SHARED, "dmc_monitor", dmc_mon->io_mem2); |
| if (r < 0) { |
| pr_err("request irq failed:%d, r:%d\n", irq, r); |
| dmc_mon = NULL; |
| return -EINVAL; |
| } |
| } |
| if (quad_dmc(dmc_mon)) { |
| irq = of_irq_get(node, 1); |
| r = request_irq(irq, dmc_monitor_irq_handler, |
| IRQF_SHARED, "dmc_monitor", dmc_mon->io_mem2); |
| if (r < 0) { |
| pr_err("request irq failed:%d, r:%d\n", irq, r); |
| dmc_mon = NULL; |
| return -EINVAL; |
| } |
| irq = of_irq_get(node, 2); |
| r = request_irq(irq, dmc_monitor_irq_handler, |
| IRQF_SHARED, "dmc_monitor", dmc_mon->io_mem3); |
| if (r < 0) { |
| pr_err("request irq failed:%d, r:%d\n", irq, r); |
| dmc_mon = NULL; |
| return -EINVAL; |
| } |
| irq = of_irq_get(node, 3); |
| r = request_irq(irq, dmc_monitor_irq_handler, |
| IRQF_SHARED, "dmc_monitor", dmc_mon->io_mem4); |
| if (r < 0) { |
| pr_err("request irq failed:%d, r:%d\n", irq, r); |
| dmc_mon = NULL; |
| return -EINVAL; |
| } |
| } |
| |
| |
| r = class_register(&dmc_monitor_class); |
| if (r) { |
| pr_err("regist dmc_monitor_class failed\n"); |
| dmc_mon = NULL; |
| return -EINVAL; |
| } |
| INIT_DELAYED_WORK(&dmc_mon->work, clear_irq_work); |
| schedule_delayed_work(&dmc_mon->work, HZ); |
| |
| if (init_dev_mask) |
| dmc_set_monitor(init_start_addr, |
| init_end_addr, init_dev_mask, 1); |
| set_dump_dmc_func(dump_dmc_reg); |
| return 0; |
| } |
| |
| static int dmc_monitor_remove(struct platform_device *pdev) |
| { |
| cancel_delayed_work_sync(&dmc_mon->work); |
| class_unregister(&dmc_monitor_class); |
| dmc_mon = NULL; |
| set_dump_dmc_func(NULL); |
| return 0; |
| } |
| |
| #ifdef CONFIG_OF |
| static const struct of_device_id dmc_monitor_match[] = { |
| #ifndef CONFIG_AMLOGIC_REMOVE_OLD |
| { |
| .compatible = "amlogic,dmc_monitor-gxl", |
| .data = (void *)DMC_TYPE_GXL, /* chip id */ |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-gxm", |
| .data = (void *)DMC_TYPE_GXM, |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-gxlx", |
| .data = (void *)DMC_TYPE_GXLX, |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-axg", |
| .data = (void *)DMC_TYPE_AXG, |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-txl", |
| .data = (void *)DMC_TYPE_TXL, |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-txlx", |
| .data = (void *)DMC_TYPE_TXLX, |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-txhd", |
| .data = (void *)DMC_TYPE_TXHD, |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-tl1", |
| .data = (void *)DMC_TYPE_TL1, |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-c1", |
| .data = (void *)DMC_TYPE_C1, |
| }, |
| #endif |
| { |
| .compatible = "amlogic,dmc_monitor-g12a", |
| .data = (void *)DMC_TYPE_G12A, |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-sm1", |
| .data = (void *)DMC_TYPE_SM1, |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-g12b", |
| .data = (void *)DMC_TYPE_G12B, |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-tm2", |
| .data = (void *)DMC_TYPE_TM2, |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-t5", |
| .data = (void *)DMC_TYPE_T5, |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-t5d", |
| .data = (void *)DMC_TYPE_T5D, |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-t5w", |
| .data = (void *)DMC_TYPE_T5W, |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-t7", |
| .data = (void *)DMC_TYPE_T7, |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-t3", |
| .data = (void *)DMC_TYPE_T3, |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-p1", |
| .data = (void *)DMC_TYPE_P1, |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-s4", |
| .data = (void *)DMC_TYPE_S4, |
| }, |
| { |
| .compatible = "amlogic,dmc_monitor-sc2", |
| .data = (void *)DMC_TYPE_SC2, |
| }, |
| {} |
| }; |
| #endif |
| |
| static struct platform_driver dmc_monitor_driver = { |
| .driver = { |
| .name = "dmc_monitor", |
| .owner = THIS_MODULE, |
| }, |
| .remove = dmc_monitor_remove, |
| }; |
| |
| int __init dmc_monitor_init(void) |
| { |
| #ifdef CONFIG_OF |
| const struct of_device_id *match_id; |
| |
| match_id = dmc_monitor_match; |
| dmc_monitor_driver.driver.of_match_table = match_id; |
| #endif |
| |
| platform_driver_probe(&dmc_monitor_driver, dmc_monitor_probe); |
| return 0; |
| } |
| |
| void dmc_monitor_exit(void) |
| { |
| platform_driver_unregister(&dmc_monitor_driver); |
| } |
| |