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>
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";
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";
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";
interrupts = <0 52 1>;
interrupt-names = "ddr_bandwidth";
};
+ dmc_monitor {
+ compatible = "amlogic, dmc_monitor";
+ status = "okay";
+ reg_base = <0xda838400>;
+ interrupts = <0 51 1>;
+ };
};
&gpu{
interrupts = <0 52 1>;
interrupt-names = "ddr_bandwidth";
};
+ dmc_monitor {
+ compatible = "amlogic, dmc_monitor";
+ status = "okay";
+ reg_base = <0xda838400>;
+ interrupts = <0 51 1>;
+ };
};
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{
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";
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.
+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
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;
}
/*
- * 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" },
{ .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" },
{ .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" },
{ .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" },
{ .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" },
{ .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" },
{ .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" },
{ .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" },
{ .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" },
{ .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" },
{ .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" },
{ .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" },
{ .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;
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;
}
--- /dev/null
+/*
+ * 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,
+};
--- /dev/null
+/*
+ * 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,
+};
--- /dev/null
+/*
+ * 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");
#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
*/
#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;
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)
{
--- /dev/null
+/*
+ * 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__ */
--- /dev/null
+/*
+ * 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__ */