ddr: add dmc monitor driver tool [1/2]
authortao zeng <tao.zeng@amlogic.com>
Wed, 22 Aug 2018 06:34:44 +0000 (14:34 +0800)
committerJianxin Pan <jianxin.pan@amlogic.com>
Wed, 29 Aug 2018 06:54:02 +0000 (23:54 -0700)
PD#172256

1. Add dts config for dmc monitor of each chip;
2. rename ddr_band_port_desc.c to ddr_port_desc.c, to reuse
   name description of each DMC port and chip;
3. Implement driver code for dmc monitor.

Change-Id: I645ded2af519b858118365d359b1d47fa455039c
Signed-off-by: tao zeng <tao.zeng@amlogic.com>
18 files changed:
MAINTAINERS
arch/arm64/boot/dts/amlogic/mesonaxg.dtsi
arch/arm64/boot/dts/amlogic/mesong12a.dtsi
arch/arm64/boot/dts/amlogic/mesong12b.dtsi
arch/arm64/boot/dts/amlogic/mesongxl.dtsi
arch/arm64/boot/dts/amlogic/mesongxm.dtsi
arch/arm64/boot/dts/amlogic/mesontxl.dtsi
arch/arm64/boot/dts/amlogic/mesontxlx.dtsi
drivers/amlogic/ddr_tool/Kconfig
drivers/amlogic/ddr_tool/Makefile
drivers/amlogic/ddr_tool/ddr_bandwidth.c
drivers/amlogic/ddr_tool/ddr_port_desc.c [moved from drivers/amlogic/ddr_tool/ddr_band_port_desc.c with 92% similarity]
drivers/amlogic/ddr_tool/dmc_g12.c [new file with mode: 0644]
drivers/amlogic/ddr_tool/dmc_gx.c [new file with mode: 0644]
drivers/amlogic/ddr_tool/dmc_monitor.c [new file with mode: 0644]
include/linux/amlogic/aml_ddr_bandwidth.h
include/linux/amlogic/ddr_port.h [new file with mode: 0644]
include/linux/amlogic/dmc_monitor.h [new file with mode: 0644]

index a84a9f0..4f707db 100644 (file)
@@ -13502,7 +13502,15 @@ F: drivers/amlogic/thermal/*
 
 M: Tao Zeng <tao.zeng@amlogic.com>
 F: drivers/amlogic/ddr_tool/*
-F: drivers/amlogic/ddr_tool/ddr_band_port_desc.c
+F: drivers/amlogic/ddr_tool/ddr_port_desc.c
+
+AMLOGIC driver for dmc protection
+M: Tao Zeng <tao.zeng@amlogic.com>
+F: drivers/amlogic/ddr_tool/dmc_g12.c
+F: drivers/amlogic/ddr_tool/dmc_gx.c
+F: drivers/amlogic/ddr_tool/dmc_monitor.c
+F: include/linux/amlogic/dmc_monitor.h
+F: include/linux/amlogic/ddr_port.h
 
 AMLOGIC driver for cpufreq
 M: Tao Zeng <tao.zeng@amlogic.com>
index cca0c31..49d84f3 100644 (file)
                interrupts = <0 52 1>;
                interrupt-names = "ddr_bandwidth";
        };
+       dmc_monitor {
+               compatible = "amlogic, dmc_monitor";
+               status = "okay";
+               reg_base = <0xff638800>;
+               interrupts = <0 51 1>;
+       };
 
        cpu_ver_name{
                compatible = "amlogic, cpu-major-id-axg";
index 2b1e033..6e4424b 100644 (file)
                interrupts = <0 52 1>;
                interrupt-names = "ddr_bandwidth";
        };
+       dmc_monitor {
+               compatible = "amlogic, dmc_monitor";
+               status = "okay";
+               reg_base = <0xff639000>;
+               interrupts = <0 51 1>;
+       };
 
        defendkey: defendkey {
        compatible = "amlogic, defendkey";
index ddda2a6..bdf231c 100644 (file)
                interrupts = <0 52 1>;
                interrupt-names = "ddr_bandwidth";
        };
+       dmc_monitor {
+               compatible = "amlogic, dmc_monitor";
+               status = "okay";
+               reg_base = <0xff639000>;
+               interrupts = <0 51 1>;
+       };
 
        isp_sc: isp-sc@ff655400 {
                compatible = "amlogic, isp-sc";
index 13a9861..f40d05e 100644 (file)
                interrupts = <0 52 1>;
                interrupt-names = "ddr_bandwidth";
        };
+       dmc_monitor {
+               compatible = "amlogic, dmc_monitor";
+               status = "okay";
+               reg_base = <0xda838400>;
+               interrupts = <0 51 1>;
+       };
 };
 
 &gpu{
index d707e53..9c92322 100644 (file)
                interrupts = <0 52 1>;
                interrupt-names = "ddr_bandwidth";
        };
+       dmc_monitor {
+               compatible = "amlogic, dmc_monitor";
+               status = "okay";
+               reg_base = <0xda838400>;
+               interrupts = <0 51 1>;
+       };
 };
 
index cf657a9..547e774 100644 (file)
                status = "okay";
        };
 
+       ddr_bandwidth {
+               compatible = "amlogic, ddr-bandwidth";
+               status = "okay";
+               reg = <0x0 0xc8838000 0x0 0x100
+                      0x0 0xc8837000 0x0 0x100>;
+               interrupts = <0 52 1>;
+               interrupt-names = "ddr_bandwidth";
+       };
+       dmc_monitor {
+               compatible = "amlogic, dmc_monitor";
+               status = "okay";
+               reg_base = <0xda838400>;
+               interrupts = <0 51 1>;
+       };
 }; /* end of / */
 
 &gpu{
index c3bf130..220efeb 100644 (file)
                interrupts = <0 52 1>;
                interrupt-names = "ddr_bandwidth";
        };
+       dmc_monitor {
+               compatible = "amlogic, dmc_monitor";
+               status = "okay";
+               reg_base = <0xff638800>;
+               interrupts = <0 51 1>;
+       };
 
        cpu_ver_name {
                compatible = "amlogic, cpu-major-id-txlx";
index b6ffb87..7e5f0f9 100644 (file)
@@ -26,3 +26,13 @@ config AMLOGIC_DDR_BANDWIDTH
          This config enables ddr bandwidth measure.
          If open it, this driver will export interface to get ddr total
          bandwidth, it can be used for ddr DVFS/devfreq system.
+
+config AMLOGIC_DMC_MONITOR
+       bool "Amlogic dmc monitor"
+       depends on AMLOGIC_DDR_TOOL
+       default y
+       help
+               DMC monitor for hardware connected to DMC master. Using it can
+               set up an address range and assign R/W permission for each
+               hardware. If hardware violated R/W permission, then interrupt
+               will generated and can help to debug memory pollution.
index a3a52b2..af6c094 100644 (file)
@@ -1,5 +1,8 @@
+obj-y += ddr_port_desc.o
 obj-$(CONFIG_AMLOGIC_DDR_WINDOW_TOOL)  += ddr_window.o
 obj-$(CONFIG_AMLOGIC_DDR_BANDWIDTH)    += ddr_bandwidth.o ddr_band_op_gxl.o \
-                                          ddr_band_op_gx.o ddr_band_op_g12.o \
-                                          ddr_band_port_desc.o
+                                          ddr_band_op_gx.o ddr_band_op_g12.o
+
+obj-$(CONFIG_AMLOGIC_DMC_MONITOR)      += dmc_monitor.o \
+                                          dmc_g12.o dmc_gx.o
 
index b161f37..e86286b 100644 (file)
@@ -281,11 +281,7 @@ static int __ref ddr_bandwidth_probe(struct platform_device *pdev)
        if (pcnt < 0)
                pr_err("can't find port descriptor,cpu:%d\n", aml_db->cpu_type);
        else {
-               aml_db->port_desc = kcalloc(pcnt, sizeof(*desc), GFP_KERNEL);
-               if (!aml_db->port_desc)
-                       goto inval;
-               pr_info("port count:%d, desc:%p\n", pcnt, aml_db->port_desc);
-               memcpy(aml_db->port_desc, desc, sizeof(*desc) * pcnt);
+               aml_db->port_desc = desc;
                aml_db->real_ports = pcnt;
        }
 
similarity index 92%
rename from drivers/amlogic/ddr_tool/ddr_band_port_desc.c
rename to drivers/amlogic/ddr_tool/ddr_port_desc.c
index 8dd9ea5..124d679 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * drivers/amlogic/ddr_tool/ddr_band_port_desc.c
+ * drivers/amlogic/ddr_tool/ddr_port_desc.c
  *
  * Copyright (C) 2017 Amlogic, Inc. All rights reserved.
  *
 #include <linux/io.h>
 #include <linux/slab.h>
 
+/*
+ * NOTE:
+ * Port "DEVICE" is total name for small bandwidth device.
+ * There are many small bandwidth devices such as nand/arb/parser
+ * connected to dmc under port "device", for better configure of
+ * these devices, re-number them with start ID of 32
+ *
+ * EXAMPLE:
+ *
+ *            DMC CONTROLLER
+ *                   |
+ *   ---------------------------------
+ *   |    |    | ..... |       | ... |
+ *  arm  mali vpu    device   vdec  hevc
+ *                     |
+ *         ------------------------
+ *         |    |     | ... |     |
+ *       emmc  ge2d  usb  audio  spicc
+ */
 static struct ddr_port_desc ddr_port_desc_m8b[] __initdata = {
        { .port_id =  0, .port_name = "ARM"           },
        { .port_id =  1, .port_name = "MALI0"         },
@@ -42,6 +61,7 @@ static struct ddr_port_desc ddr_port_desc_m8b[] __initdata = {
        { .port_id = 11, .port_name = "VDEC"          },
        { .port_id = 12, .port_name = "HCODEC"        },
        { .port_id = 14, .port_name = "AUDIO"         },
+       { .port_id = 15, .port_name = "DEVICE"        },
        /* start of each device */
        { .port_id = 33, .port_name = "NAND"          },
        { .port_id = 34, .port_name = "BLKMV"         },
@@ -64,6 +84,7 @@ static struct ddr_port_desc ddr_port_desc_gxbb[] __initdata = {
        { .port_id =  2, .port_name = "MALI1"         },
        { .port_id =  3, .port_name = "HDCP"          },
        { .port_id =  4, .port_name = "HEVC"          },
+       { .port_id =  7, .port_name = "DEVICE"        },
        { .port_id =  8, .port_name = "VPU READ1"     },
        { .port_id =  9, .port_name = "VPU READ2"     },
        { .port_id = 10, .port_name = "VPU READ3"     },
@@ -98,6 +119,7 @@ static struct ddr_port_desc ddr_port_desc_gxl[] __initdata = {
        { .port_id =  4, .port_name = "HEVC"          },
        { .port_id =  5, .port_name = "TEST"          },
        { .port_id =  6, .port_name = "USB3.0"        },
+       { .port_id =  7, .port_name = "DEVICE"        },
        { .port_id =  8, .port_name = "VPU READ1"     },
        { .port_id =  9, .port_name = "VPU READ2"     },
        { .port_id = 10, .port_name = "VPU READ3"     },
@@ -132,6 +154,7 @@ static struct ddr_port_desc ddr_port_desc_gxm[] __initdata = {
        { .port_id =  4, .port_name = "HEVC"          },
        { .port_id =  5, .port_name = "TEST"          },
        { .port_id =  6, .port_name = "USB3.0"        },
+       { .port_id =  7, .port_name = "DEVICE"        },
        { .port_id =  8, .port_name = "VPU READ1"     },
        { .port_id =  9, .port_name = "VPU READ2"     },
        { .port_id = 10, .port_name = "VPU READ3"     },
@@ -166,6 +189,7 @@ static struct ddr_port_desc ddr_port_desc_gxlx[] __initdata = {
        { .port_id =  4, .port_name = "HEVC"          },
        { .port_id =  5, .port_name = "H265ENC/TEST"  },
        { .port_id =  6, .port_name = "USB3.0"        },
+       { .port_id =  7, .port_name = "DEVICE"        },
        { .port_id =  8, .port_name = "VPU READ1"     },
        { .port_id =  9, .port_name = "VPU READ2"     },
        { .port_id = 10, .port_name = "VPU READ3"     },
@@ -200,6 +224,7 @@ static struct ddr_port_desc ddr_port_desc_g12a[] __initdata = {
        { .port_id =  4, .port_name = "HEVC FRONT"    },
        { .port_id =  5, .port_name = "TEST"          },
        { .port_id =  6, .port_name = "USB3.0"        },
+       { .port_id =  7, .port_name = "DEVICE"        },
        { .port_id =  8, .port_name = "HEVC BACK"     },
        { .port_id =  9, .port_name = "H265ENC"       },
        { .port_id = 16, .port_name = "VPU READ1"     },
@@ -235,6 +260,7 @@ static struct ddr_port_desc ddr_port_desc_g12b[] __initdata = {
        { .port_id =  4, .port_name = "HEVC FRONT"    },
        { .port_id =  5, .port_name = "TEST"          },
        { .port_id =  6, .port_name = "USB3.0"        },
+       { .port_id =  7, .port_name = "DEVICE"        },
        { .port_id =  8, .port_name = "HEVC BACK"     },
        { .port_id =  9, .port_name = "H265ENC"       },
        { .port_id = 10, .port_name = "NNA"           },
@@ -275,6 +301,7 @@ static struct ddr_port_desc ddr_port_desc_axg[] __initdata = {
        { .port_id =  4, .port_name = "AUDIO"         },
        { .port_id =  5, .port_name = "TEST"          },
        { .port_id =  6, .port_name = "USB3.0"        },
+       { .port_id =  7, .port_name = "DEVICE"        },
        { .port_id =  8, .port_name = "VPU READ0"     },
        /* start of each device */
        { .port_id = 34, .port_name = "DMA"           },
@@ -296,6 +323,7 @@ static struct ddr_port_desc ddr_port_desc_txl[] __initdata = {
        { .port_id =  4, .port_name = "HEVC"          },
        { .port_id =  5, .port_name = "TEST"          },
        { .port_id =  6, .port_name = "USB3.0"        },
+       { .port_id =  7, .port_name = "DEVICE"        },
        { .port_id =  8, .port_name = "VPU READ1"     },
        { .port_id =  9, .port_name = "VPU READ2"     },
        { .port_id = 10, .port_name = "VPU READ3"     },
@@ -331,6 +359,7 @@ static struct ddr_port_desc ddr_port_desc_txlx[] __initdata = {
        { .port_id =  4, .port_name = "HEVC"          },
        { .port_id =  5, .port_name = "TEST"          },
        { .port_id =  6, .port_name = "USB3.0"        },
+       { .port_id =  7, .port_name = "DEVICE"        },
        { .port_id =  8, .port_name = "VPU READ1"     },
        { .port_id =  9, .port_name = "VPU READ2"     },
        { .port_id = 10, .port_name = "VPU READ3"     },
@@ -365,6 +394,7 @@ static struct ddr_port_desc ddr_port_desc_txhd[] __initdata = {
        { .port_id =  4, .port_name = "HEVC"          },
        { .port_id =  5, .port_name = "TEST"          },
        { .port_id =  6, .port_name = "USB3.0"        },
+       { .port_id =  7, .port_name = "DEVICE"        },
        { .port_id =  8, .port_name = "VPU READ1"     },
        { .port_id =  9, .port_name = "VPU READ2"     },
        { .port_id = 10, .port_name = "VPU READ3"     },
@@ -391,10 +421,18 @@ static struct ddr_port_desc ddr_port_desc_txhd[] __initdata = {
        { .port_id = 47, .port_name = "DEMOD"         }
 };
 
+static struct ddr_port_desc *chip_ddr_port;
+static unsigned char chip_ddr_port_num;
+
 int __init ddr_find_port_desc(int cpu_type, struct ddr_port_desc **desc)
 {
        int desc_size = -EINVAL;
 
+       if (chip_ddr_port) {
+               *desc = chip_ddr_port;
+               return chip_ddr_port_num;
+       }
+
        switch (cpu_type) {
        case MESON_CPU_MAJOR_ID_M8B:
                *desc = ddr_port_desc_m8b;
@@ -459,5 +497,13 @@ int __init ddr_find_port_desc(int cpu_type, struct ddr_port_desc **desc)
                break;
        }
 
+       /* using once */
+       chip_ddr_port = kcalloc(desc_size, sizeof(*chip_ddr_port), GFP_KERNEL);
+       if (!chip_ddr_port)
+               return -ENOMEM;
+       memcpy(chip_ddr_port, *desc, sizeof(*chip_ddr_port) * desc_size);
+       chip_ddr_port_num = desc_size;
+       *desc = chip_ddr_port;
+
        return desc_size;
 }
diff --git a/drivers/amlogic/ddr_tool/dmc_g12.c b/drivers/amlogic/ddr_tool/dmc_g12.c
new file mode 100644 (file)
index 0000000..a8db417
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ * drivers/amlogic/ddr_tool/dmc_g12.c
+ *
+ * Copyright (C) 2017 Amlogic, Inc. All rights reserved.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ */
+
+#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/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/cpu_version.h>
+#include <linux/amlogic/page_trace.h>
+#include <linux/amlogic/dmc_monitor.h>
+#include <linux/amlogic/ddr_port.h>
+
+#define DMC_PROT0_RANGE                ((0x00a0  << 2))
+#define DMC_PROT0_CTRL         ((0x00a1  << 2))
+#define DMC_PROT1_RANGE                ((0x00a2  << 2))
+#define DMC_PROT1_CTRL         ((0x00a3  << 2))
+#define DMC_SEC_STATUS         ((0x00b8  << 2))
+#define DMC_VIO_ADDR0          ((0x00b9  << 2))
+#define DMC_VIO_ADDR1          ((0x00ba  << 2))
+#define DMC_VIO_ADDR2          ((0x00bb  << 2))
+#define DMC_VIO_ADDR3          ((0x00bc  << 2))
+
+#define DMC_VIO_PROT_RANGE0    (1 << 21)
+#define DMC_VIO_PROT_RANGE1    (1 << 22)
+
+static size_t g12_dmc_dump_reg(char *buf)
+{
+       size_t sz = 0, i;
+       unsigned long val;
+
+       val = dmc_rw(DMC_PROT0_RANGE, 0, DMC_READ);
+       sz += sprintf(buf + sz, "DMC_PROT0_RANGE:%lx\n", val);
+       val = dmc_rw(DMC_PROT0_CTRL, 0, DMC_READ);
+       sz += sprintf(buf + sz, "DMC_PROT0_CTRL:%lx\n", val);
+       val = dmc_rw(DMC_PROT1_RANGE, 0, DMC_READ);
+       sz += sprintf(buf + sz, "DMC_PROT1_RANGE:%lx\n", val);
+       val = dmc_rw(DMC_PROT1_CTRL, 0, DMC_READ);
+       sz += sprintf(buf + sz, "DMC_PROT1_CTRL:%lx\n", val);
+       val = dmc_rw(DMC_SEC_STATUS, 0, DMC_READ);
+       sz += sprintf(buf + sz, "DMC_SEC_STATUS:%lx\n", val);
+       for (i = 0; i < 4; i++) {
+               val = dmc_rw(DMC_VIO_ADDR0 + (i << 2), 0, DMC_READ);
+               sz += sprintf(buf + sz, "DMC_VIO_ADDR%ld:%lx\n", i, val);
+       }
+
+       return sz;
+}
+
+static void check_violation(struct dmc_monitor *mon)
+{
+       int i, port, subport;
+       unsigned long addr, status;
+       struct page *page;
+       unsigned long *p;
+       char id_str[4];
+
+       for (i = 1; i < 4; i += 2) {
+               status = dmc_rw(DMC_VIO_ADDR0 + (i << 2), 0, DMC_READ);
+               if (!(status & DMC_VIO_PROT_RANGE0))
+                       continue;
+               addr = dmc_rw(DMC_VIO_ADDR0 + ((i - 1) << 2), 0,
+                             DMC_READ);
+               if (addr > mon->addr_end)
+                       continue;
+
+               /* ignore violation on same page/same port */
+               if ((addr & PAGE_MASK) == mon->last_addr &&
+                   status == mon->last_status) {
+                       mon->same_page++;
+                       continue;
+               }
+
+               port = (status >> 13) & 0x1f;
+               subport = (status >> 6) & 0xf;
+               pr_info(DMC_TAG", addr:%08lx, s:%08lx, ID:%s, sub:%s, c:%ld\n",
+                       addr, status, to_ports(port),
+                       to_sub_ports(port, subport, id_str), mon->same_page);
+               if (pfn_valid(__phys_to_pfn(addr))) {
+                       page = phys_to_page(addr);
+                       p = (page_address(page) + (addr & (PAGE_SIZE - 1)));
+                       pr_info(DMC_TAG" [%08lx]:%016lx, f:%8lx, m:%p, a:%pf\n",
+                               addr, *p, page->flags & 0xffffffff,
+                               page->mapping,
+                               (void *)get_page_trace(page));
+               }
+               if (!port) /* dump stack for CPU write */
+                       dump_stack();
+
+               mon->same_page   = 0;
+               mon->last_addr   = addr & PAGE_MASK;
+               mon->last_status = status;
+       }
+}
+
+static void g12_dmc_mon_irq(struct dmc_monitor *mon)
+{
+       unsigned long value;
+
+       value = dmc_rw(DMC_SEC_STATUS, 0, DMC_READ);
+       if (value & DMC_WRITE_VIOLATION)
+               check_violation(mon);
+
+       /* check irq flags just after IRQ handler */
+       if (in_interrupt())
+               mod_delayed_work(system_wq, &mon->work, 0);
+       /* clear irq */
+       dmc_rw(DMC_SEC_STATUS, value, DMC_WRITE);
+}
+
+static int g12_dmc_mon_set(struct dmc_monitor *mon)
+{
+       unsigned long value, end;
+
+       /* aligned to 64KB */
+       end = ALIGN(mon->addr_end, DMC_ADDR_SIZE);
+       value = (mon->addr_start >> 16) | ((end >> 16) << 16);
+       dmc_rw(DMC_PROT0_RANGE, value, DMC_WRITE);
+
+       value = (1 << 24) | mon->device;
+       dmc_rw(DMC_PROT0_CTRL, value, DMC_WRITE);
+
+       pr_info("range:%08lx - %08lx, device:%x\n",
+               mon->addr_start, mon->addr_end, mon->device);
+       return 0;
+}
+
+void g12_dmc_mon_disable(struct dmc_monitor *mon)
+{
+       dmc_rw(DMC_PROT0_RANGE, 0, DMC_WRITE);
+       dmc_rw(DMC_PROT0_CTRL, 0, DMC_WRITE);
+       mon->device     = 0;
+       mon->addr_start = 0;
+       mon->addr_end   = 0;
+}
+
+struct dmc_mon_ops g12_dmc_mon_ops = {
+       .handle_irq = g12_dmc_mon_irq,
+       .set_montor = g12_dmc_mon_set,
+       .disable    = g12_dmc_mon_disable,
+       .dump_reg   = g12_dmc_dump_reg,
+};
diff --git a/drivers/amlogic/ddr_tool/dmc_gx.c b/drivers/amlogic/ddr_tool/dmc_gx.c
new file mode 100644 (file)
index 0000000..c5d0941
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * drivers/amlogic/ddr_tool/dmc_gx.c
+ *
+ * Copyright (C) 2017 Amlogic, Inc. All rights reserved.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ */
+
+#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/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/cpu_version.h>
+#include <linux/amlogic/page_trace.h>
+#include <linux/amlogic/dmc_monitor.h>
+#include <linux/amlogic/ddr_port.h>
+
+#define DMC_PROT0_RANGE                ((0x00a0  << 2))
+#define DMC_PROT0_CTRL         ((0x00a1  << 2))
+#define DMC_PROT1_RANGE                ((0x00a2  << 2))
+#define DMC_PROT1_CTRL         ((0x00a3  << 2))
+#define DMC_SEC_STATUS         ((0x00b6  << 2))
+#define DMC_VIO_ADDR0          ((0x00b7  << 2))
+#define DMC_VIO_ADDR1          ((0x00b8  << 2))
+#define DMC_VIO_ADDR2          ((0x00b9  << 2))
+#define DMC_VIO_ADDR3          ((0x00ba  << 2))
+#define DMC_VIO_ADDR4          ((0x00bb  << 2))
+#define DMC_VIO_ADDR5          ((0x00bc  << 2))
+#define DMC_VIO_ADDR6          ((0x00bd  << 2))
+#define DMC_VIO_ADDR7          ((0x00be  << 2))
+
+#define DMC_VIO_PROT_RANGE0    (1 << 20)
+#define DMC_VIO_PROT_RANGE1    (1 << 21)
+
+static size_t gx_dmc_dump_reg(char *buf)
+{
+       size_t sz = 0, i;
+       unsigned long val;
+
+       val = dmc_rw(DMC_PROT0_RANGE, 0, DMC_READ);
+       sz += sprintf(buf + sz, "DMC_PROT0_RANGE:%lx\n", val);
+       val = dmc_rw(DMC_PROT0_CTRL, 0, DMC_READ);
+       sz += sprintf(buf + sz, "DMC_PROT0_CTRL:%lx\n", val);
+       val = dmc_rw(DMC_PROT1_RANGE, 0, DMC_READ);
+       sz += sprintf(buf + sz, "DMC_PROT1_RANGE:%lx\n", val);
+       val = dmc_rw(DMC_PROT1_CTRL, 0, DMC_READ);
+       sz += sprintf(buf + sz, "DMC_PROT1_CTRL:%lx\n", val);
+       val = dmc_rw(DMC_SEC_STATUS, 0, DMC_READ);
+       sz += sprintf(buf + sz, "DMC_SEC_STATUS:%lx\n", val);
+       for (i = 0; i < 8; i++) {
+               val = dmc_rw(DMC_VIO_ADDR0 + (i << 2), 0, DMC_READ);
+               sz += sprintf(buf + sz, "DMC_VIO_ADDR%ld:%lx\n", i, val);
+       }
+
+       return sz;
+}
+
+static void check_violation(struct dmc_monitor *mon)
+{
+       int i, port, subport;
+       unsigned long addr, status;
+       struct page *page;
+       unsigned long *p;
+       char id_str[4];
+
+       for (i = 1; i < 8; i += 2) {
+               status = dmc_rw(DMC_VIO_ADDR0 + (i << 2), 0, DMC_READ);
+               if (!(status & DMC_VIO_PROT_RANGE0))
+                       continue;
+               addr = dmc_rw(DMC_VIO_ADDR0 + ((i - 1) << 2), 0,
+                             DMC_READ);
+               if (addr > mon->addr_end)
+                       continue;
+
+               /* ignore violation on same page/same port */
+               if ((addr & PAGE_MASK) == mon->last_addr &&
+                   status == mon->last_status) {
+                       mon->same_page++;
+                       continue;
+               }
+
+               port = (status >> 10) & 0xf;
+               subport = (status >> 6) & 0xf;
+               pr_info(DMC_TAG", addr:%08lx, s:%08lx, ID:%s, sub:%s, c:%ld\n",
+                       addr, status, to_ports(port),
+                       to_sub_ports(port, subport, id_str), mon->same_page);
+               if (pfn_valid(__phys_to_pfn(addr))) {
+                       page = phys_to_page(addr);
+                       p = (page_address(page) + (addr & (PAGE_SIZE - 1)));
+                       pr_info(DMC_TAG" [%08lx]:%016lx, f:%8lx, m:%p, a:%pf\n",
+                               addr, *p, page->flags & 0xffffffff,
+                               page->mapping,
+                               (void *)get_page_trace(page));
+               }
+               if (!port) /* dump stack for CPU write */
+                       dump_stack();
+
+               mon->same_page   = 0;
+               mon->last_addr   = addr & PAGE_MASK;
+               mon->last_status = status;
+       }
+}
+
+static void gx_dmc_mon_irq(struct dmc_monitor *mon)
+{
+       unsigned long value;
+
+       value = dmc_rw(DMC_SEC_STATUS, 0, DMC_READ);
+       if (value & DMC_WRITE_VIOLATION)
+               check_violation(mon);
+
+       /* check irq flags just after IRQ handler */
+       if (in_interrupt())
+               mod_delayed_work(system_wq, &mon->work, 0);
+       /* clear irq */
+       dmc_rw(DMC_SEC_STATUS, value, DMC_WRITE);
+}
+
+static int gx_dmc_mon_set(struct dmc_monitor *mon)
+{
+       unsigned long value, end;
+
+       /* aligned to 64KB */
+       end = ALIGN(mon->addr_end, DMC_ADDR_SIZE);
+       value = (mon->addr_start >> 16) | ((end >> 16) << 16);
+       dmc_rw(DMC_PROT0_RANGE, value, DMC_WRITE);
+
+       value = (1 << 19) | mon->device;
+       dmc_rw(DMC_PROT0_CTRL, value, DMC_WRITE);
+
+       pr_info("range:%08lx - %08lx, device:%x\n",
+               mon->addr_start, mon->addr_end, mon->device);
+       return 0;
+}
+
+void gx_dmc_mon_disable(struct dmc_monitor *mon)
+{
+       dmc_rw(DMC_PROT0_RANGE, 0, DMC_WRITE);
+       dmc_rw(DMC_PROT0_CTRL, 0, DMC_WRITE);
+       mon->device     = 0;
+       mon->addr_start = 0;
+       mon->addr_end   = 0;
+}
+
+struct dmc_mon_ops gx_dmc_mon_ops = {
+       .handle_irq = gx_dmc_mon_irq,
+       .set_montor = gx_dmc_mon_set,
+       .disable    = gx_dmc_mon_disable,
+       .dump_reg   = gx_dmc_dump_reg,
+};
diff --git a/drivers/amlogic/ddr_tool/dmc_monitor.c b/drivers/amlogic/ddr_tool/dmc_monitor.c
new file mode 100644 (file)
index 0000000..0a35538
--- /dev/null
@@ -0,0 +1,373 @@
+/*
+ * drivers/amlogic/ddr_tool/dmc_monitor.c
+ *
+ * Copyright (C) 2017 Amlogic, Inc. All rights reserved.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ */
+
+#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/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/cpu_version.h>
+#include <linux/amlogic/page_trace.h>
+#include <linux/arm-smccc.h>
+#include <linux/amlogic/dmc_monitor.h>
+#include <linux/amlogic/ddr_port.h>
+
+static struct dmc_monitor *dmc_mon;
+
+unsigned long dmc_rw(unsigned long addr, unsigned long value, int rw)
+{
+       struct arm_smccc_res smccc;
+
+       arm_smccc_smc(DMC_MON_RW, addr + dmc_mon->io_base,
+                     value, rw, 0, 0, 0, 0, &smccc);
+
+       return smccc.a0;
+}
+
+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)
+                       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 i;
+}
+
+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;
+
+       /* 7 is device port id */
+       if (mid == 7) {
+               for (i = 0; i < dmc_mon->port_num; i++) {
+                       if (dmc_mon->port[i].port_id == sid + PORT_MAJOR)
+                               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;
+
+       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 size_t dump_reg(char *buf)
+{
+       size_t sz = 0, i;
+
+       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");
+       for (i = 0; i < sizeof(dmc_mon->device) * 8; i++) {
+               if (dmc_mon->device & (1 << 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);
+
+       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);
+}
+
+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;
+       if (en)
+               dmc_mon->device |= dev_mask;
+       else
+               dmc_mon->device &= ~(dev_mask);
+       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 || id >= BITS_PER_LONG)
+               return -EINVAL;
+
+       return dmc_set_monitor(start, end, 1 << 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 protect_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 protect_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 ssize_t dev_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 (!strncmp(buf, "all", 3))
+               dmc_mon->device = get_all_dev_mask();
+       else {
+               i = dev_name_to_id(buf);
+               if (i < 0) {
+                       pr_info("bad device:%s\n", buf);
+                       return -EINVAL;
+               }
+               dmc_mon->device |= (1 << i);
+       }
+       dmc_set_monitor(dmc_mon->addr_start, dmc_mon->addr_end,
+                       dmc_mon->device, 1);
+
+       return count;
+}
+
+static ssize_t dev_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)
+                       break;
+               s += sprintf(buf + s, "%2d:%s\n",
+                       dmc_mon->port[i].port_id,
+                       dmc_mon->port[i].port_name);
+       }
+       return s;
+}
+
+static ssize_t dump_show(struct class *cla,
+       struct class_attribute *attr, char *buf)
+{
+       return dump_reg(buf);
+}
+
+static struct class_attribute dmc_monitor_attr[] = {
+       __ATTR(range, 0664, protect_range_show, protect_range_store),
+       __ATTR(device, 0664, dev_show, dev_store),
+       __ATTR_RO(dump),
+       __ATTR_NULL
+};
+
+static struct class dmc_monitor_class = {
+       .name = "dmc_monitor",
+       .class_attrs = dmc_monitor_attr,
+};
+
+static int dmc_monitor_probe(struct platform_device *pdev)
+{
+       int r = 0, irq, ports;
+       unsigned int io;
+       struct device_node *node = pdev->dev.of_node;
+       struct ddr_port_desc *desc;
+
+       pr_info("%s\n", __func__);
+       r = get_cpu_type();
+       dmc_mon = kzalloc(sizeof(struct dmc_monitor), GFP_KERNEL);
+       if (!dmc_mon)
+               return -ENOMEM;
+
+       ports = ddr_find_port_desc(r, &desc);
+       if (ports < 0) {
+               pr_info("can't get port desc\n");
+               goto inval;
+       }
+       dmc_mon->chip = r;
+       dmc_mon->port_num = ports;
+       dmc_mon->port = desc;
+       if (dmc_mon->chip >= MESON_CPU_MAJOR_ID_G12A)
+               dmc_mon->ops = &g12_dmc_mon_ops;
+       else
+               dmc_mon->ops = &gx_dmc_mon_ops;
+
+       r = of_property_read_u32(node, "reg_base", &io);
+       if (r < 0) {
+               pr_info("can't find iobase\n");
+               goto inval;
+       }
+
+       dmc_mon->io_base = io;
+
+       irq = of_irq_get(node, 0);
+       r = request_irq(irq, dmc_monitor_irq_handler,
+                       IRQF_SHARED, "dmc_monitor", pdev);
+       if (r < 0) {
+               pr_info("request irq failed:%d, r:%d\n", irq, r);
+               goto inval;
+       }
+       r = class_register(&dmc_monitor_class);
+       if (r) {
+               pr_err("regist dmc_monitor_class failed\n");
+               goto inval;
+       }
+       INIT_DELAYED_WORK(&dmc_mon->work, clear_irq_work);
+       schedule_delayed_work(&dmc_mon->work, HZ);
+
+       return 0;
+inval:
+       kfree(dmc_mon);
+       dmc_mon = NULL;
+       return -EINVAL;
+}
+
+static int dmc_monitor_remove(struct platform_device *pdev)
+{
+       cancel_delayed_work_sync(&dmc_mon->work);
+       class_unregister(&dmc_monitor_class);
+       kfree(dmc_mon);
+       dmc_mon = NULL;
+       return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id dmc_monitor_match[] = {
+       {
+               .compatible = "amlogic, dmc_monitor",
+       },
+       {}
+};
+#endif
+
+static struct platform_driver dmc_monitor_driver = {
+       .driver = {
+               .name  = "dmc_monitor",
+               .owner = THIS_MODULE,
+       #ifdef CONFIG_OF
+               .of_match_table = dmc_monitor_match,
+       #endif
+       },
+       .probe = dmc_monitor_probe,
+       .remove = dmc_monitor_remove,
+};
+
+static int __init dmc_monitor_init(void)
+{
+       int ret;
+
+       ret = platform_driver_register(&dmc_monitor_driver);
+       return ret;
+}
+
+static void __exit dmc_monitor_exit(void)
+{
+       platform_driver_unregister(&dmc_monitor_driver);
+}
+
+module_init(dmc_monitor_init);
+module_exit(dmc_monitor_exit);
+MODULE_DESCRIPTION("amlogic dmc monitor driver");
+MODULE_LICENSE("GPL");
index 7982156..5e3113b 100644 (file)
 
 #define DMC_QOS_IRQ                    (1 << 30)
 #define MAX_CHANNEL                    4
-#define MAX_PORTS                      256
-#define MAX_NAME                       15
-#define PORT_MAJOR                     32
 
+#include <linux/amlogic/ddr_port.h>
 /*
  * register offset for chips before g12
  */
@@ -87,11 +85,6 @@ struct ddr_bandwidth_ops {
 #endif
 };
 
-struct ddr_port_desc {
-       char port_name[MAX_NAME];
-       unsigned char port_id;
-};
-
 struct ddr_bandwidth {
        void __iomem *ddr_reg;
        void __iomem *pll_reg;
@@ -114,7 +107,6 @@ extern unsigned int aml_get_ddr_usage(void);
 extern struct ddr_bandwidth_ops g12_ddr_bw_ops;
 extern struct ddr_bandwidth_ops gx_ddr_bw_ops;
 extern struct ddr_bandwidth_ops gxl_ddr_bw_ops;
-extern int ddr_find_port_desc(int cpu_type, struct ddr_port_desc **desc);
 #else
 static inline unsigned int aml_get_ddr_usage(void)
 {
diff --git a/include/linux/amlogic/ddr_port.h b/include/linux/amlogic/ddr_port.h
new file mode 100644 (file)
index 0000000..a50755f
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * include/linux/amlogic/ddr_port.h
+ *
+ * Copyright (C) 2017 Amlogic, Inc. All rights reserved.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ */
+
+#ifndef __DDR_PORT_DESC_H__
+#define __DDR_PORT_DESC_H__
+
+#define MAX_PORTS                      256
+#define MAX_NAME                       15
+#define PORT_MAJOR                     32
+
+
+struct ddr_port_desc {
+       char port_name[MAX_NAME];
+       unsigned char port_id;
+};
+
+extern int ddr_find_port_desc(int cpu_type, struct ddr_port_desc **desc);
+#endif /* __DDR_PORT_DESC_H__ */
diff --git a/include/linux/amlogic/dmc_monitor.h b/include/linux/amlogic/dmc_monitor.h
new file mode 100644 (file)
index 0000000..9fe2e34
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * include/linux/amlogic/dmc_monitor.h
+ *
+ * Copyright (C) 2017 Amlogic, Inc. All rights reserved.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ */
+
+#ifndef __DMC_MONITOR_H__
+#define __DMC_MONITOR_H__
+
+#define PROTECT_READ           (1 << 0)
+#define PROTECT_WRITE          (1 << 1)
+
+#define DMC_MON_RW             0x8200004A
+
+#define DMC_READ               0
+#define DMC_WRITE              1
+
+#define DMC_WRITE_VIOLATION    (1 << 1)
+
+/*
+ * Address is aligned to 64 KB
+ */
+#define DMC_ADDR_SIZE          (0x10000)
+#define DMC_TAG                        "DMC VIOLATION"
+
+struct dmc_monitor;
+struct dmc_mon_ops {
+       void (*handle_irq)(struct dmc_monitor *mon);
+       int  (*set_montor)(struct dmc_monitor *mon);
+       void (*disable)(struct dmc_monitor *mon);
+       size_t (*dump_reg)(char *buf);
+};
+
+struct dmc_monitor {
+       unsigned long io_base;
+       unsigned long addr_start;
+       unsigned long addr_end;
+       unsigned int  device;
+       unsigned short port_num;
+       unsigned short chip;
+       unsigned long last_addr;
+       unsigned long same_page;
+       unsigned long last_status;
+       struct ddr_port_desc *port;
+       struct dmc_mon_ops   *ops;
+       struct delayed_work work;
+};
+
+extern void dmc_monitor_disable(void);
+
+/*
+ * start: physical start address, aligned to 64KB
+ * end: physical end address, aligned to 64KB
+ * dev_mask: device bit to set
+ * en: 0: close monitor, 1: enable monitor
+ */
+extern int dmc_set_monitor(unsigned long start, unsigned long end,
+                          unsigned long dev_mask, int en);
+
+/*
+ * start: physical start address, aligned to 64KB
+ * end: physical end address, aligned to 64KB
+ * port_name: name of port to set, see ddr_port_desc for each chip in
+ *            drivers/amlogic/ddr_tool/ddr_port_desc.c
+ * en: 0: close monitor, 1: enable monitor
+ */
+extern int dmc_set_monitor_by_name(unsigned long start, unsigned long end,
+                   const char *port_name, int en);
+
+extern unsigned int get_all_dev_mask(void);
+
+/*
+ * Following functions are internal used only
+ */
+extern unsigned long dmc_rw(unsigned long addr, unsigned long value, int rw);
+
+extern char *to_ports(int id);
+extern char *to_sub_ports(int mid, int sid, char *id_str);
+
+extern struct dmc_mon_ops gx_dmc_mon_ops;
+extern struct dmc_mon_ops g12_dmc_mon_ops;
+
+#endif /* __DMC_MONITOR_H__ */