mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-07 11:26:02 +09:00
ddr: add dmc monitor driver tool [1/2]
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>
This commit is contained in:
10
MAINTAINERS
10
MAINTAINERS
@@ -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>
|
||||
|
||||
@@ -800,6 +800,12 @@
|
||||
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";
|
||||
|
||||
@@ -1782,6 +1782,12 @@
|
||||
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";
|
||||
|
||||
@@ -1826,6 +1826,12 @@
|
||||
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";
|
||||
|
||||
@@ -1347,6 +1347,12 @@
|
||||
interrupts = <0 52 1>;
|
||||
interrupt-names = "ddr_bandwidth";
|
||||
};
|
||||
dmc_monitor {
|
||||
compatible = "amlogic, dmc_monitor";
|
||||
status = "okay";
|
||||
reg_base = <0xda838400>;
|
||||
interrupts = <0 51 1>;
|
||||
};
|
||||
};
|
||||
|
||||
&gpu{
|
||||
|
||||
@@ -1430,5 +1430,11 @@
|
||||
interrupts = <0 52 1>;
|
||||
interrupt-names = "ddr_bandwidth";
|
||||
};
|
||||
dmc_monitor {
|
||||
compatible = "amlogic, dmc_monitor";
|
||||
status = "okay";
|
||||
reg_base = <0xda838400>;
|
||||
interrupts = <0 51 1>;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1035,6 +1035,20 @@
|
||||
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{
|
||||
|
||||
@@ -928,6 +928,12 @@
|
||||
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";
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
@@ -31,6 +31,25 @@
|
||||
#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;
|
||||
}
|
||||
171
drivers/amlogic/ddr_tool/dmc_g12.c
Normal file
171
drivers/amlogic/ddr_tool/dmc_g12.c
Normal file
@@ -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,
|
||||
};
|
||||
175
drivers/amlogic/ddr_tool/dmc_gx.c
Normal file
175
drivers/amlogic/ddr_tool/dmc_gx.c
Normal file
@@ -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,
|
||||
};
|
||||
373
drivers/amlogic/ddr_tool/dmc_monitor.c
Normal file
373
drivers/amlogic/ddr_tool/dmc_monitor.c
Normal file
@@ -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");
|
||||
@@ -24,10 +24,8 @@
|
||||
|
||||
#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)
|
||||
{
|
||||
|
||||
32
include/linux/amlogic/ddr_port.h
Normal file
32
include/linux/amlogic/ddr_port.h
Normal file
@@ -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__ */
|
||||
94
include/linux/amlogic/dmc_monitor.h
Normal file
94
include/linux/amlogic/dmc_monitor.h
Normal file
@@ -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__ */
|
||||
Reference in New Issue
Block a user