1372 lines
46 KiB
C
1372 lines
46 KiB
C
/*
|
|
* Copyright (c) 2016 Samsung Electronics Co., Ltd.
|
|
* http://www.samsung.com
|
|
*
|
|
* IPs Traffic Monitor(ITMON) Driver for Samsung Exynos8895 SOC
|
|
* By Hosung Kim (hosung0.kim@samsung.com)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/io.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/of_irq.h>
|
|
#include <soc/samsung/exynos-itmon.h>
|
|
|
|
|
|
#define OFFSET_TMOUT_REG (0x2000)
|
|
#define OFFSET_REQ_R (0x0)
|
|
#define OFFSET_REQ_W (0x20)
|
|
#define OFFSET_RESP_R (0x40)
|
|
#define OFFSET_RESP_W (0x60)
|
|
#define OFFSET_ERR_REPT (0x20)
|
|
#define OFFSET_NUM (0x4)
|
|
|
|
#define REG_INT_MASK (0x0)
|
|
#define REG_INT_CLR (0x4)
|
|
#define REG_INT_INFO (0x8)
|
|
#define REG_EXT_INFO_0 (0x10)
|
|
#define REG_EXT_INFO_1 (0x14)
|
|
#define REG_EXT_INFO_2 (0x18)
|
|
|
|
#define REG_DBG_CTL (0x10)
|
|
#define REG_TMOUT_INIT_VAL (0x14)
|
|
#define REG_TMOUT_FRZ_EN (0x18)
|
|
#define REG_TMOUT_BUF_WR_OFFSET (0x10)
|
|
#define REG_TMOUT_BUF_RD_ADDR (0x20)
|
|
#define REG_TMOUT_BUF_RD_ID (0x24)
|
|
#define REG_TMOUT_BUF_RD_PAYLOAD (0x28)
|
|
#define REG_TMOUT_BUF_WR_ADDR (0x30)
|
|
#define REG_TMOUT_BUF_WR_ID (0x34)
|
|
#define REG_TMOUT_BUF_WR_PAYLOAD (0x38)
|
|
|
|
#define BIT_ERR_CODE(x) (((x) & (0xF << 28)) >> 28)
|
|
#define BIT_ERR_OCCURRED(x) (((x) & (0x1 << 27)) >> 27)
|
|
#define BIT_ERR_VALID(x) (((x) & (0x1 << 26)) >> 26)
|
|
#define BIT_AXID(x) (((x) & (0xFFFF)))
|
|
#define BIT_AXUSER(x) (((x) & (0xFFFF << 16)) >> 16)
|
|
#define BIT_AXBURST(x) (((x) & (0x3)))
|
|
#define BIT_AXPROT(x) (((x) & (0x3 << 2)) >> 2)
|
|
#define BIT_AXLEN(x) (((x) & (0xF << 16)) >> 16)
|
|
#define BIT_AXSIZE(x) (((x) & (0x7 << 28)) >> 28)
|
|
|
|
#define M_NODE (0)
|
|
#define T_S_NODE (1)
|
|
#define T_M_NODE (2)
|
|
#define S_NODE (3)
|
|
#define NODE_TYPE (4)
|
|
|
|
#define ERRCODE_SLVERR (0)
|
|
#define ERRCODE_DECERR (1)
|
|
#define ERRCODE_UNSUPORTED (2)
|
|
#define ERRCODE_POWER_DOWN (3)
|
|
#define ERRCODE_UNKNOWN_4 (4)
|
|
#define ERRCODE_UNKNOWN_5 (5)
|
|
#define ERRCODE_TMOUT (6)
|
|
|
|
#define BUS_DATA (0)
|
|
#define BUS_PERI (1)
|
|
#define BUS_PATH_TYPE (2)
|
|
|
|
#define TRANS_TYPE_WRITE (0)
|
|
#define TRANS_TYPE_READ (1)
|
|
#define TRANS_TYPE_NUM (2)
|
|
|
|
#define FROM_CP (0)
|
|
#define FROM_CPU (1)
|
|
#define FROM_CPU_MAY (2)
|
|
#define FROM_M_NODE (3)
|
|
|
|
#define TMOUT (0xFFFFF)
|
|
#define TMOUT_TEST (0x1)
|
|
|
|
#define PANIC_ALLOWED_THRESHOLD (0x2)
|
|
#define INVALID_REMAPPING (0x08000000)
|
|
|
|
static bool initial_multi_irq_enable = false;
|
|
static struct itmon_dev *g_itmon = NULL;
|
|
|
|
struct itmon_rpathinfo {
|
|
unsigned int id;
|
|
char *port_name;
|
|
char *dest_name;
|
|
unsigned int bits;
|
|
unsigned int shift_bits;
|
|
};
|
|
|
|
struct itmon_masterinfo {
|
|
char *port_name;
|
|
unsigned int user;
|
|
char *master_name;
|
|
unsigned int bits;
|
|
};
|
|
|
|
struct itmon_nodegroup;
|
|
|
|
struct itmon_traceinfo {
|
|
char* port;
|
|
char* master;
|
|
char* dest;
|
|
unsigned long target_addr;
|
|
unsigned int errcode;
|
|
bool read;
|
|
bool path_dirty;
|
|
bool trans_dirty;
|
|
bool dirty;
|
|
unsigned long from;
|
|
char buf[SZ_32];
|
|
};
|
|
|
|
struct itmon_tracedata {
|
|
unsigned int int_info;
|
|
unsigned int ext_info_0;
|
|
unsigned int ext_info_1;
|
|
unsigned int ext_info_2;
|
|
unsigned int offset;
|
|
bool logging;
|
|
bool read;
|
|
};
|
|
|
|
struct itmon_nodeinfo {
|
|
unsigned int type;
|
|
char *name;
|
|
unsigned int phy_regs;
|
|
void __iomem *regs;
|
|
unsigned int time_val;
|
|
bool tmout_enabled;
|
|
bool tmout_frz_enabled;
|
|
bool err_enabled;
|
|
bool retention;
|
|
struct itmon_tracedata tracedata;
|
|
struct itmon_nodegroup *group;
|
|
struct list_head list;
|
|
};
|
|
|
|
const static char *itmon_pathtype[] = {
|
|
"DATA Path transaction (0x2000_0000 ~ 0xf_ffff_ffff)",
|
|
"PERI(SFR) Path transaction (0x0 ~ 0x1fff_ffff)",
|
|
};
|
|
|
|
/* Error Code Description */
|
|
const static char *itmon_errcode[] = {
|
|
"Error Detect by the Slave(SLVERR)",
|
|
"Decode error(DECERR)",
|
|
"Unsupported transaction error",
|
|
"Power Down access error",
|
|
"Unsupported transaction",
|
|
"Unsupported transaction",
|
|
"Timeout error - response timeout in timeout value",
|
|
"Invalid errorcode",
|
|
};
|
|
|
|
const static char *itmon_nodestring[] = {
|
|
"M_NODE",
|
|
"TAXI_S_NODE",
|
|
"TAXI_M_NODE",
|
|
"S_NODE",
|
|
};
|
|
|
|
struct itmon_nodegroup {
|
|
int irq;
|
|
char *name;
|
|
unsigned int phy_regs;
|
|
void __iomem *regs;
|
|
struct itmon_nodeinfo *nodeinfo;
|
|
unsigned int nodesize;
|
|
unsigned int bus_type;
|
|
};
|
|
|
|
struct itmon_platdata {
|
|
const struct itmon_rpathinfo *rpathinfo;
|
|
const struct itmon_masterinfo *masterinfo;
|
|
struct itmon_nodegroup *nodegroup;
|
|
struct itmon_traceinfo traceinfo[BUS_PATH_TYPE];
|
|
struct list_head tracelist[BUS_PATH_TYPE];
|
|
unsigned int err_cnt;
|
|
bool panic_allowed;
|
|
bool probed;
|
|
};
|
|
|
|
const static struct itmon_rpathinfo rpathinfo[] = {
|
|
/* Target Address = 0x2000_0000 ~ 0xf_ffff_ffff */
|
|
{0, "DPU0", "DREX", GENMASK(5, 0), 0},
|
|
{1, "DPU1", "DREX", GENMASK(5, 0), 0},
|
|
{2, "DPU2", "DREX", GENMASK(5, 0), 0},
|
|
{3, "CAM0", "DREX", GENMASK(5, 0), 0},
|
|
{4, "CAM1", "DREX", GENMASK(5, 0), 0},
|
|
{5, "ISPLP", "DREX", GENMASK(5, 0), 0},
|
|
{6, "SRDZ", "DREX", GENMASK(5, 0), 0},
|
|
{7, "IVA", "DREX", GENMASK(5, 0), 0},
|
|
{8, "DSP", "DREX", GENMASK(5, 0), 0},
|
|
{9, "VPU", "DREX", GENMASK(5, 0), 0},
|
|
{10, "MFC0", "DREX", GENMASK(5, 0), 0},
|
|
{11, "MFC1", "DREX", GENMASK(5, 0), 0},
|
|
{12, "G2D0", "DREX", GENMASK(5, 0), 0},
|
|
{13, "G2D1", "DREX", GENMASK(5, 0), 0},
|
|
{14, "G2D2", "DREX", GENMASK(5, 0), 0},
|
|
{15, "VTS", "DREX", GENMASK(5, 0), 0},
|
|
{16, "FSYS0", "DREX", GENMASK(5, 0), 0},
|
|
{17, "CORESIGHT", "DREX", GENMASK(5, 0), 0},
|
|
{18, "PDMA", "DREX", GENMASK(5, 0), 0},
|
|
{19, "SPDMA", "DREX", GENMASK(5, 0), 0},
|
|
{20, "FSYS1", "DREX", GENMASK(5, 0), 0},
|
|
{21, "GNSS", "DREX", GENMASK(5, 0), 0},
|
|
{22, "ALIVE", "DREX", GENMASK(5, 0), 0},
|
|
{23, "ABOX", "DREX", GENMASK(5, 0), 0},
|
|
{24, "DPU0", "DREX", GENMASK(5, 0), 0},
|
|
{25, "DPU1", "DREX", GENMASK(5, 0), 0},
|
|
{26, "DPU2", "DREX", GENMASK(5, 0), 0},
|
|
{27, "CAM0", "DREX", GENMASK(5, 0), 0},
|
|
{28, "CAM1", "DREX", GENMASK(5, 0), 0},
|
|
{29, "ISPLP", "DREX", GENMASK(5, 0), 0},
|
|
{30, "SRDZ", "DREX", GENMASK(5, 0), 0},
|
|
{31, "IVA", "DREX", GENMASK(5, 0), 0},
|
|
{32, "DSP", "DREX", GENMASK(5, 0), 0},
|
|
{33, "VPU", "DREX", GENMASK(5, 0), 0},
|
|
{34, "MFC0", "DREX", GENMASK(5, 0), 0},
|
|
{35, "MFC1", "DREX", GENMASK(5, 0), 0},
|
|
{36, "G2D0", "DREX", GENMASK(5, 0), 0},
|
|
{37, "G2D1", "DREX", GENMASK(5, 0), 0},
|
|
{38, "G2D2", "DREX", GENMASK(5, 0), 0},
|
|
{39, "VTS", "DREX", GENMASK(5, 0), 0},
|
|
{40, "FSYS0", "DREX", GENMASK(5, 0), 0},
|
|
{41, "CORESIGHT", "DREX", GENMASK(5, 0), 0},
|
|
{42, "PDMA", "DREX", GENMASK(5, 0), 0},
|
|
{43, "SPDMA", "DREX", GENMASK(5, 0), 0},
|
|
{44, "FSYS1", "DREX", GENMASK(5, 0), 0},
|
|
{45, "GNSS", "DREX", GENMASK(5, 0), 0},
|
|
{46, "ALIVE", "DREX", GENMASK(5, 0), 0},
|
|
{47, "ABOX", "DREX", GENMASK(5, 0), 0},
|
|
{48, "G3D0", "DREX", GENMASK(5, 0), 0},
|
|
{49, "G3D1", "DREX", GENMASK(5, 0), 0},
|
|
{50, "G3D2", "DREX", GENMASK(5, 0), 0},
|
|
{51, "G3D3", "DREX", GENMASK(5, 0), 0},
|
|
{52, "CP", "DREX", GENMASK(5, 0), 0},
|
|
/* Target Address = 0x1800_0000 ~ 0x1fff_ffff */
|
|
{0, "DPU0", "SP", GENMASK(4, 0), 0},
|
|
{1, "DPU1", "SP", GENMASK(4, 0), 0},
|
|
{2, "DPU2", "SP", GENMASK(4, 0), 0},
|
|
{3, "CAM0", "SP", GENMASK(4, 0), 0},
|
|
{4, "CAM1", "SP", GENMASK(4, 0), 0},
|
|
{5, "ISPLP", "SP", GENMASK(4, 0), 0},
|
|
{6, "SRDZ", "SP", GENMASK(4, 0), 0},
|
|
{7, "IVA", "SP", GENMASK(4, 0), 0},
|
|
{8, "DSP", "SP", GENMASK(4, 0), 0},
|
|
{9, "VPU", "SP", GENMASK(4, 0), 0},
|
|
{10, "MFC0", "SP", GENMASK(4, 0), 0},
|
|
{11, "MFC1", "SP", GENMASK(4, 0), 0},
|
|
{12, "G2D0", "SP", GENMASK(4, 0), 0},
|
|
{13, "G2D1", "SP", GENMASK(4, 0), 0},
|
|
{14, "G2D2", "SP", GENMASK(4, 0), 0},
|
|
{15, "VTS", "SP", GENMASK(4, 0), 0},
|
|
{16, "FSYS0", "SP", GENMASK(4, 0), 0},
|
|
{17, "CORESIGHT", "SP", GENMASK(4, 0), 0},
|
|
{18, "PDMA", "SP", GENMASK(4, 0), 0},
|
|
{19, "SPDMA", "SP", GENMASK(4, 0), 0},
|
|
{20, "FSYS1", "SP", GENMASK(4, 0), 0},
|
|
{21, "GNSS", "SP", GENMASK(4, 0), 0},
|
|
{22, "ALIVE", "SP", GENMASK(4, 0), 0},
|
|
{23, "ABOX", "SP", GENMASK(4, 0), 0},
|
|
/* Target Address = 0x0000_0000 ~ 0x17ff_ffff */
|
|
{0, "DPU0", "PERI", GENMASK(4, 0), 0},
|
|
{1, "DPU1", "PERI", GENMASK(4, 0), 0},
|
|
{2, "DPU2", "PERI", GENMASK(4, 0), 0},
|
|
{3, "CAM0", "PERI", GENMASK(4, 0), 0},
|
|
{4, "CAM1", "PERI", GENMASK(4, 0), 0},
|
|
{5, "ISPLP", "PERI", GENMASK(4, 0), 0},
|
|
{6, "SRDZ", "PERI", GENMASK(4, 0), 0},
|
|
{7, "IVA", "PERI", GENMASK(4, 0), 0},
|
|
{8, "DSP", "PERI", GENMASK(4, 0), 0},
|
|
{9, "VPU", "PERI", GENMASK(4, 0), 0},
|
|
{10, "MFC0", "PERI", GENMASK(4, 0), 0},
|
|
{11, "MFC1", "PERI", GENMASK(4, 0), 0},
|
|
{12, "G2D0", "PERI", GENMASK(4, 0), 0},
|
|
{13, "G2D1", "PERI", GENMASK(4, 0), 0},
|
|
{14, "G2D2", "PERI", GENMASK(4, 0), 0},
|
|
{15, "ABOX", "PERI", GENMASK(4, 0), 0},
|
|
{16, "VTS", "PERI", GENMASK(4, 0), 0},
|
|
{17, "FSYS0", "PERI", GENMASK(4, 0), 0},
|
|
{18, "CORESIGHT", "PERI", GENMASK(4, 0), 0},
|
|
{19, "PDMA", "PERI", GENMASK(4, 0), 0},
|
|
{20, "SPDMA", "PERI", GENMASK(4, 0), 0},
|
|
{21, "FSYS1", "PERI", GENMASK(4, 0), 0},
|
|
{22, "GNSS", "PERI", GENMASK(4, 0), 0},
|
|
{22, "ALIVE", "PERI", GENMASK(4, 0), 0},
|
|
};
|
|
|
|
const static struct itmon_masterinfo masterinfo[] = {
|
|
{"DPU0", 0, /* 0XXXX0 */ "DPP/WBMUX", BIT(5) | BIT(0)},
|
|
{"DPU0", BIT(0), /* 0XXXX1 */ "SYSMMU_DPU0", BIT(5) | BIT(0)},
|
|
|
|
{"DPU1", 0, /* 0XXXX0 */ "DPP/WBMUX", BIT(5) | BIT(0)},
|
|
{"DPU1", BIT(0), /* 0XXXX1 */ "SYSMMU_DPU1", BIT(5) | BIT(0)},
|
|
|
|
{"DPU2", 0, /* 0XXXX0 */ "DPP/WBMUX", BIT(5) | BIT(0)},
|
|
{"DPU2", BIT(0), /* 0XXXX1 */ "SYSMMU_DPU2", BIT(5) | BIT(0)},
|
|
|
|
{"CAM0", 0, /* 0XX000 */ "CSISx4", BIT(5) | GENMASK(2, 0)},
|
|
{"CAM0", BIT(1), /* 0XX010 */ "TPU0", BIT(5) | GENMASK(2, 0)},
|
|
{"CAM0", BIT(2), /* 0XX100 */ "VRA", BIT(5) | GENMASK(2, 0)},
|
|
{"CAM0", GENMASK(2, 1), /* 0XX110 */ "TPU1", BIT(5) | GENMASK(2, 0)},
|
|
{"CAM0", BIT(0), /* 0XXXX1 */ "SYSMMU_CAM0", BIT(5) | BIT(0)},
|
|
|
|
{"CAM1", 0, /* 0XXXX0 */ "MC_SCALER", BIT(5) | BIT(0)},
|
|
{"CAM1", BIT(0), /* 0XXXX1 */ "SYSMMU_CAM1", BIT(5) | BIT(0)},
|
|
|
|
{"ISPLP", 0, /* 0XX000 */ "3AAW", BIT(5) | GENMASK(2, 0)},
|
|
{"ISPLP", BIT(1), /* 0XX010 */ "ISPLP", BIT(5) | GENMASK(2, 0)},
|
|
{"ISPLP", BIT(2), /* 0XX100 */ "ISPHQ", BIT(5) | GENMASK(2, 0)},
|
|
{"ISPLP", BIT(0), /* 0XXXX1 */ "SYSMMU_ISPLP", BIT(5) | BIT(0)},
|
|
|
|
{"FSYS0", 0, /* 0XXX00 */ "ETR", BIT(5) | GENMASK(1, 0)},
|
|
{"FSYS0", BIT(1), /* 0XXX10 */ "USB", BIT(5) | GENMASK(1, 0)},
|
|
{"FSYS0", BIT(0), /* 0XXXX1 */ "UFS", BIT(5) | BIT(0)},
|
|
|
|
{"MFC0", 0, /* 0XXXX0 */ "MFC0", BIT(5) | BIT(0)},
|
|
{"MFC0", BIT(0), /* 0XXXX1 */ "SYSMMU_MFC0", BIT(5) | BIT(0)},
|
|
|
|
{"MFC1", 0, /* 0XXXX0 */ "MFC1", BIT(5) | BIT(0)},
|
|
{"MFC1", BIT(0), /* 0XXXX1 */ "SYSMMU_MFC1", BIT(5) | BIT(0)},
|
|
|
|
{"G2D0", 0, /* 0XXXX0 */ "G2D0", BIT(5) | BIT(0)},
|
|
{"G2D0", BIT(0), /* 0XXXX1 */ "SYSMMU_G2D0", BIT(5) | BIT(0)},
|
|
|
|
{"G2D1", 0, /* 0XXXX0 */ "G2D1", BIT(5) | BIT(0)},
|
|
{"G2D1", BIT(0), /* 0XXXX1 */ "SYSMMU_G2D1", BIT(5) | BIT(0)},
|
|
|
|
{"G2D2", 0, /* 0XXX00 */ "JPEG", BIT(5) | GENMASK(1, 0)},
|
|
{"G2D2", BIT(1), /* 0XXX10 */ "M2MSCALER", BIT(5) | GENMASK(1, 0)},
|
|
{"G2D2", BIT(0), /* 0XXXX1 */ "SYSMMU_G2D2", BIT(5) | BIT(0)},
|
|
|
|
{"IVA", 0, /* 0XXX00 */ "DSP0", BIT(5) | GENMASK(1, 0)},
|
|
{"IVA", BIT(1), /* 0XXX10 */ "DSP1", BIT(5) | GENMASK(1, 0)},
|
|
{"IVA", BIT(0), /* 0XXXX1 */ "SYSMMU_DSP", BIT(5) | BIT(0)},
|
|
|
|
{"VPU", 0, /* 0XXXX0 */ "VPU", BIT(5) | BIT(0)},
|
|
{"VPU", BIT(0), /* 0XXXX1 */ "SYSMMU_VPU", BIT(5) | BIT(0)},
|
|
|
|
{"FSYS1", 0, /* 0XX000 */ "MMC2", BIT(5) | GENMASK(2, 0)},
|
|
{"FSYS1", BIT(0), /* 0XX001 */ "PCIE0", BIT(5) | GENMASK(2, 0)},
|
|
{"FSYS1", BIT(1), /* 0XX010 */ "PCIE1", BIT(5) | GENMASK(2, 0)},
|
|
{"FSYS1", GENMASK(1, 0), /* 0XX011 */ "SSS", BIT(5) | GENMASK(2, 0)},
|
|
{"FSYS1", BIT(2), /* 0XX100 */ "RTIC", BIT(5) | GENMASK(2, 0)},
|
|
{"FSYS1", BIT(2) | BIT(0), /* 0XX101 */ "MCOMP", BIT(5) | GENMASK(2, 0)},
|
|
|
|
{"AUD", 0, /* 0XXXX0 */ "SPUS/SPUM/CA7", BIT(5) | BIT(0)},
|
|
{"AUD", BIT(0), /* 0XXXX1 */ "SYSMMU_AUC", BIT(5) | BIT(0)},
|
|
|
|
{"GNSS", 0, /* 0XXX00 */ "CM0+", BIT(5) | GENMASK(1, 0)},
|
|
{"GNSS", BIT(0), /* 0XXX01 */ "XDMA0", BIT(5) | GENMASK(1, 0)},
|
|
{"GNSS", BIT(1), /* 0XXX10 */ "XDMA1", BIT(5) | GENMASK(1, 0)},
|
|
|
|
{"ALIVE", 0, /* 0XXX00 */ "CM3", BIT(5) | GENMASK(1, 0)},
|
|
{"ALIVE", BIT(0), /* 0XXX01 */ "SCAN2AXI", BIT(5) | GENMASK(1, 0)},
|
|
{"ALIVE", BIT(1), /* 0XXX10 */ "CM4F/DMIC", BIT(5) | GENMASK(1, 0)},
|
|
|
|
{"CP", BIT(3), /* X01XXX */ "CR7M", GENMASK(4, 3)},
|
|
{"CP", BIT(2), /* X001XX */ "CR4MtoL2", GENMASK(4, 2)},
|
|
{"CP", GENMASK(4, 3), /* X1100X */ "DMA", GENMASK(4, 1)},
|
|
{"CP", GENMASK(4, 3) | BIT(1), /* X1101X */ "MDMtoL2", GENMASK(4, 1)},
|
|
{"CP", BIT(4), /* X1000X */ "LMACtoL2", GENMASK(4, 1)},
|
|
{"CP", BIT(1), /* X00010 */ "CSXAP", GENMASK(4, 1)},
|
|
{"CP", BIT(4) | GENMASK(1, 0), /* X10011 */ "HARQMOVERtoL2", GENMASK(4, 0)},
|
|
|
|
/* Others */
|
|
{"SRDZ", 0, "SRDZ", 0},
|
|
{"DSP", 0, "DSP", 0},
|
|
{"ABOX", 0, "ABOX", 0},
|
|
{"VTS", 0, "VTS", 0},
|
|
{"PDMA", 0, "PDMA", 0},
|
|
{"SPDMA",0, "SPDMA",0},
|
|
};
|
|
|
|
static struct itmon_nodeinfo data_bus_1[] = {
|
|
{M_NODE, "ALIVE", 0x15423000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "FSYS1", 0x15403000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "GNSS", 0x15413000, NULL, 0, false, false, true, false},
|
|
{T_S_NODE, "BUS1_B0", 0x15433000, NULL, TMOUT, true, false, true, false},
|
|
};
|
|
|
|
static struct itmon_nodeinfo data_bus_c[] = {
|
|
{M_NODE, "ABOX", 0x15143000, NULL, 0, false, false, true, false},
|
|
{T_M_NODE, "BUS1_B0", 0x15003000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "CAM0", 0x15043000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "CAM1", 0x15053000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "CORESIGHT", 0x15113000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "DPU0", 0x15013000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "DPU1", 0x15023000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "DPU2", 0x15033000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "DSP", 0x15093000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "FSYS0", 0x15103000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "G2D0", 0x150D3000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "G2D1", 0x150E3000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "G2D2", 0x150F3000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "ISPLP", 0x15063000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "IVA", 0x15083000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "MFC0", 0x150B3000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "MFC1", 0x150C3000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "PDMA", 0x15123000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "SPDMA", 0x15133000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "SRDZ", 0x15073000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "VPU", 0x150A3000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "VTS", 0x15153000, NULL, 0, false, false, true, false},
|
|
{T_S_NODE, "BUSC_M0", 0x15163000, NULL, 0, false, false, true, false},
|
|
{T_S_NODE, "BUSC_M1", 0x15173000, NULL, 0, false, false, true, false},
|
|
{T_S_NODE, "BUSC_M2", 0x15183000, NULL, 0, false, false, true, false},
|
|
{T_S_NODE, "BUSC_M3", 0x15193000, NULL, 0, false, false, true, false},
|
|
{S_NODE, "PERI", 0x151B3000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "SP", 0x151A3000, NULL, TMOUT, true, false, true, false},
|
|
};
|
|
|
|
static struct itmon_nodeinfo data_core[] = {
|
|
{T_M_NODE, "BUSC_M0", 0x14A03000, NULL, 0, false, false, true, false},
|
|
{T_M_NODE, "BUSC_M1", 0x14A13000, NULL, 0, false, false, true, false},
|
|
{T_M_NODE, "BUSC_M2", 0x14A23000, NULL, 0, false, false, true, false},
|
|
{T_M_NODE, "BUSC_M3", 0x14A33000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "CP", 0x14A83000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "G3D0", 0x14A43000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "G3D1", 0x14A53000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "G3D2", 0x14A63000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "G3D3", 0x14A73000, NULL, 0, false, false, true, false},
|
|
{S_NODE, "DREX", 0x14A93000, NULL, TMOUT, true, true, true, false},
|
|
{S_NODE, "DREX", 0x14AA3000, NULL, TMOUT, true, true, true, false},
|
|
};
|
|
|
|
static struct itmon_nodeinfo peri_core_0[] = {
|
|
{M_NODE, "CP_PERI_M", 0x14C63000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "SCI_CCM0", 0x14C33000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "SCI_CCM1", 0x14C43000, NULL, 0, false, false, true, false},
|
|
{M_NODE, "SCI_IRPM", 0x14C53000, NULL, 0, false, false, true, false},
|
|
{T_S_NODE, "CORE0_CORE1", 0x14C03000, NULL, 0, true, false, true, false},
|
|
{T_S_NODE, "CORE_BUSC", 0x14C13000, NULL, 0, true, false, true, false},
|
|
};
|
|
|
|
static struct itmon_nodeinfo peri_core_1[] = {
|
|
{T_M_NODE, "BUSC_CORE", 0x14E13000, NULL, 0, false, false, true, false},
|
|
{T_M_NODE, "CORE0_CORE1", 0x14E03000, NULL, 0, false, false, true, false},
|
|
{S_NODE, "CORESIGHT", 0x14E83000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "CORE_SFR", 0x14E33000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "CPUCL0", 0x14E53000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "CPUCL1", 0x14E63000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "G3D", 0x14E73000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "IMEM", 0x14E43000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "TREX_CORE", 0x14E23000, NULL, TMOUT, true, false, true, false},
|
|
};
|
|
|
|
static struct itmon_nodeinfo peri_bus_c[] = {
|
|
{M_NODE, "BUSC_PERI_M", 0x153A3000, NULL, 0, false, false, true, false},
|
|
{T_M_NODE, "CORE_BUSC", 0x153B3000, NULL, 0, false, false, true, false},
|
|
{S_NODE, "ABOX", 0x15383000, NULL, TMOUT, true, false, true, false},
|
|
{T_S_NODE, "BUSC_BUS1", 0x15243000, NULL, 0, false, false, true, false},
|
|
{T_S_NODE, "BUSC_CORE", 0x15233000, NULL, 0, false, false, true, false},
|
|
{S_NODE, "BUSC_SFR0", 0x15213000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "BUSC_SFR1", 0x15223000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "CAM", 0x152E3000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "DPU0", 0x152C3000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "DPU1", 0x152D3000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "DSP", 0x15363000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "FSYS0", 0x15333000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "G2D", 0x15343000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "ISPHQ", 0x15313000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "ISPLP", 0x15303000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "IVA", 0x15353000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "MFC", 0x15323000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "MIF0", 0x15253000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "MIF1", 0x15263000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "MIF2", 0x15273000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "MIF3", 0x15283000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "PERIC0", 0x152A3000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "PERIC1", 0x152B3000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "PERIS", 0x15293000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "SRDZ", 0x152F3000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "TREX_BUSC", 0x15203000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "VPU", 0x15373000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "VTS", 0x15393000, NULL, TMOUT, true, false, true, false},
|
|
};
|
|
|
|
static struct itmon_nodeinfo peri_bus_1[] = {
|
|
{T_M_NODE, "BUSC_BUS1", 0x15643000, NULL, 0, false, false, true, false},
|
|
{S_NODE, "ALIVE", 0x15633000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "BUS1_SFR", 0x15613000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "FSYS1", 0x15623000, NULL, TMOUT, true, false, true, false},
|
|
{S_NODE, "TREX_BUS1", 0x15603000, NULL, TMOUT, true, false, true, false},
|
|
};
|
|
|
|
static struct itmon_nodegroup nodegroup[] = {
|
|
{72, "DATA_BUS_1", 0x155F3000, NULL, data_bus_1, ARRAY_SIZE(data_bus_1), BUS_DATA},
|
|
{311, "DATA_CORE", 0x14AF3000, NULL, data_core, ARRAY_SIZE(data_core), BUS_DATA},
|
|
{92, "DATA_BUS_C", 0, NULL, data_bus_c, ARRAY_SIZE(data_bus_c), BUS_DATA},
|
|
{315, "PERI_CORE_0", 0x14CF3000, NULL, peri_core_0, ARRAY_SIZE(peri_core_0),BUS_PERI},
|
|
{316, "PERI_CORE_1", 0x14EF3000, NULL, peri_core_1, ARRAY_SIZE(peri_core_1),BUS_PERI},
|
|
{93, "PERI_BUS_C", 0x153F3000, NULL, peri_bus_c, ARRAY_SIZE(peri_bus_c), BUS_PERI},
|
|
{77, "PERI_BUS_1", 0x156F3000, NULL, peri_bus_1, ARRAY_SIZE(peri_bus_1), BUS_PERI},
|
|
};
|
|
|
|
struct itmon_dev {
|
|
struct device *dev;
|
|
struct itmon_platdata *pdata;
|
|
struct of_device_id *match;
|
|
int irq;
|
|
int id;
|
|
void __iomem *regs;
|
|
spinlock_t ctrl_lock;
|
|
struct itmon_notifier notifier_info;
|
|
};
|
|
|
|
struct itmon_panic_block {
|
|
struct notifier_block nb_panic_block;
|
|
struct itmon_dev *pdev;
|
|
};
|
|
|
|
/* declare notifier_list */
|
|
static ATOMIC_NOTIFIER_HEAD(itmon_notifier_list);
|
|
|
|
static const struct of_device_id itmon_dt_match[] = {
|
|
{.compatible = "samsung,exynos-itmon",
|
|
.data = NULL,},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, itmon_dt_match);
|
|
|
|
static struct itmon_rpathinfo *itmon_get_rpathinfo(struct itmon_dev *itmon,
|
|
unsigned int id,
|
|
char *dest_name)
|
|
{
|
|
struct itmon_platdata *pdata = itmon->pdata;
|
|
struct itmon_rpathinfo *rpath = NULL;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(rpathinfo); i++) {
|
|
if (pdata->rpathinfo[i].id == (id & pdata->rpathinfo[i].bits)) {
|
|
if (dest_name && !strncmp(pdata->rpathinfo[i].dest_name,
|
|
dest_name,
|
|
strlen(pdata->rpathinfo[i].dest_name))) {
|
|
rpath = (struct itmon_rpathinfo *)&pdata->rpathinfo[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return rpath;
|
|
}
|
|
|
|
static struct itmon_masterinfo *itmon_get_masterinfo(struct itmon_dev *itmon,
|
|
char *port_name,
|
|
unsigned int user)
|
|
{
|
|
struct itmon_platdata *pdata = itmon->pdata;
|
|
struct itmon_masterinfo *master = NULL;
|
|
unsigned int val;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(masterinfo); i++) {
|
|
if (!strncmp(pdata->masterinfo[i].port_name, port_name, strlen(port_name))) {
|
|
val = user & pdata->masterinfo[i].bits;
|
|
if (val == pdata->masterinfo[i].user) {
|
|
master = (struct itmon_masterinfo *)&pdata->masterinfo[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return master;
|
|
}
|
|
|
|
static void itmon_init(struct itmon_dev *itmon, bool enabled)
|
|
{
|
|
struct itmon_platdata *pdata = itmon->pdata;
|
|
struct itmon_nodeinfo *node;
|
|
unsigned int offset;
|
|
int i, j;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(nodegroup); i++) {
|
|
node = pdata->nodegroup[i].nodeinfo;
|
|
for (j = 0; j < pdata->nodegroup[i].nodesize; j++) {
|
|
if (node[j].type == S_NODE && node[j].tmout_enabled) {
|
|
offset = OFFSET_TMOUT_REG;
|
|
/* Enable Timeout setting */
|
|
__raw_writel(enabled, node[j].regs + offset + REG_DBG_CTL);
|
|
/* set tmout interval value */
|
|
__raw_writel(node[j].time_val,
|
|
node[j].regs + offset + REG_TMOUT_INIT_VAL);
|
|
pr_debug("Exynos IPM - %s timeout enabled\n", node[j].name);
|
|
if (node[j].tmout_frz_enabled) {
|
|
/* Enable freezing */
|
|
__raw_writel(enabled,
|
|
node[j].regs + offset + REG_TMOUT_FRZ_EN);
|
|
}
|
|
}
|
|
if (node[j].err_enabled) {
|
|
/* clear previous interrupt of req_read */
|
|
offset = OFFSET_REQ_R;
|
|
if (!pdata->probed || !node->retention)
|
|
__raw_writel(1, node[j].regs + offset + REG_INT_CLR);
|
|
/* enable interrupt */
|
|
__raw_writel(enabled, node[j].regs + offset + REG_INT_MASK);
|
|
|
|
/* clear previous interrupt of req_write */
|
|
offset = OFFSET_REQ_W;
|
|
if (pdata->probed || !node->retention)
|
|
__raw_writel(1, node[j].regs + offset + REG_INT_CLR);
|
|
/* enable interrupt */
|
|
__raw_writel(enabled, node[j].regs + offset + REG_INT_MASK);
|
|
|
|
/* clear previous interrupt of response_read */
|
|
offset = OFFSET_RESP_R;
|
|
if (!pdata->probed || !node->retention)
|
|
__raw_writel(1, node[j].regs + offset + REG_INT_CLR);
|
|
/* enable interrupt */
|
|
__raw_writel(enabled, node[j].regs + offset + REG_INT_MASK);
|
|
|
|
/* clear previous interrupt of response_write */
|
|
offset = OFFSET_RESP_W;
|
|
if (!pdata->probed || !node->retention)
|
|
__raw_writel(1, node[j].regs + offset + REG_INT_CLR);
|
|
/* enable interrupt */
|
|
__raw_writel(enabled, node[j].regs + offset + REG_INT_MASK);
|
|
pr_debug("Exynos IPM - %s error reporting enabled\n", node[j].name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void itmon_enable(bool enabled)
|
|
{
|
|
if (g_itmon) {
|
|
itmon_init(g_itmon, enabled);
|
|
}
|
|
}
|
|
|
|
void itmon_set_errcnt(int cnt)
|
|
{
|
|
struct itmon_platdata *pdata;
|
|
if (g_itmon) {
|
|
pdata = g_itmon->pdata;
|
|
pdata->err_cnt = cnt;
|
|
}
|
|
}
|
|
|
|
static void itmon_post_handler_by_master(struct itmon_dev *itmon,
|
|
struct itmon_nodeinfo *node,
|
|
unsigned int trans_type)
|
|
{
|
|
struct itmon_platdata *pdata = itmon->pdata;
|
|
struct itmon_traceinfo *traceinfo = &pdata->traceinfo[trans_type];
|
|
|
|
/* After treatment by port */
|
|
if (!traceinfo->port || strlen(traceinfo->port) < 1)
|
|
return;
|
|
|
|
if (!strncmp(traceinfo->port, "CP", strlen(traceinfo->port))) {
|
|
/* if master is DSP and operation is read, we don't care this */
|
|
if (traceinfo->master && traceinfo->target_addr == INVALID_REMAPPING &&
|
|
!strncmp(traceinfo->master, "CR4MtoL2", strlen(traceinfo->master))) {
|
|
pdata->err_cnt = 0;
|
|
pr_info("ITMON skips CP's DSP(CR4MtoL2) detected\n");
|
|
} else {
|
|
/* Disable busmon all interrupts */
|
|
itmon_init(itmon, false);
|
|
}
|
|
} else if (!strncmp(traceinfo->port, "CPU", strlen("CPU"))) {
|
|
/* if master is CPU, then we expect any exception */
|
|
if (pdata->err_cnt > PANIC_ALLOWED_THRESHOLD) {
|
|
pdata->err_cnt = 0;
|
|
itmon_init(itmon, false);
|
|
pr_info("ITMON is turn-off when CPU transaction is detected repeatly\n");
|
|
} else {
|
|
pr_info("ITMON skips CPU transaction detected\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void itmon_report_timeout(struct itmon_dev *itmon,
|
|
struct itmon_nodeinfo *node,
|
|
unsigned int trans_type)
|
|
{
|
|
unsigned int id, payload, axid, user, valid, timeout;
|
|
char *master_name, *port_name;
|
|
struct itmon_rpathinfo *port;
|
|
struct itmon_masterinfo *master;
|
|
int i, num = (trans_type == TRANS_TYPE_READ ? SZ_128 : SZ_64);
|
|
int fz_offset = (trans_type == TRANS_TYPE_READ ? 0 : REG_TMOUT_BUF_WR_OFFSET);
|
|
|
|
pr_info(" TIMEOUT_BUFFER Information\n\n");
|
|
pr_info(" > NUM| BLOCK| MASTER| VALID| TIMEOUT| ID| PAYLOAD|\n");
|
|
|
|
for (i = 0; i < num; i++) {
|
|
writel(i, node->regs + OFFSET_TMOUT_REG +
|
|
REG_TMOUT_BUF_RD_ADDR + fz_offset);
|
|
id = readl(node->regs + OFFSET_TMOUT_REG +
|
|
REG_TMOUT_BUF_RD_ID + fz_offset);
|
|
payload = readl(node->regs + OFFSET_TMOUT_REG +
|
|
REG_TMOUT_BUF_RD_PAYLOAD + fz_offset);
|
|
|
|
/* ID[5:0] 6bit : R-PATH */
|
|
axid = id & GENMASK(5, 0);
|
|
/* PAYLOAD[15:8] : USER */
|
|
user = payload & GENMASK(15, 8) >> 8;
|
|
/* PAYLOAD[0] : Valid or Not valid */
|
|
valid = payload & BIT(0);
|
|
/* PAYLOAD[19:16] : Timeout */
|
|
timeout = payload & GENMASK(19, 16) >> 16;
|
|
|
|
port = (struct itmon_rpathinfo *)
|
|
itmon_get_rpathinfo(itmon, axid, "DREX");
|
|
if (port) {
|
|
port_name = port->port_name;
|
|
master = (struct itmon_masterinfo *)
|
|
itmon_get_masterinfo(itmon, port_name, user);
|
|
if (master)
|
|
master_name = master->master_name;
|
|
else
|
|
master_name = "Unknown";
|
|
} else {
|
|
port_name = "Unknown";
|
|
master_name = "Unknown";
|
|
}
|
|
pr_info(" > %03d|%8s|%8s|%8u|%8x|%08x|%08x|\n",
|
|
i, port_name, master_name, valid, timeout, id, payload);
|
|
}
|
|
pr_info("--------------------------------------------------------------------------\n");
|
|
}
|
|
|
|
static unsigned int power(unsigned int param, unsigned int num)
|
|
{
|
|
if (num == 0)
|
|
return 1;
|
|
return param * (power(param, num - 1));
|
|
}
|
|
|
|
static void itmon_report_traceinfo(struct itmon_dev *itmon,
|
|
struct itmon_nodeinfo *node,
|
|
unsigned int trans_type)
|
|
{
|
|
struct itmon_platdata *pdata = itmon->pdata;
|
|
struct itmon_traceinfo *traceinfo = &pdata->traceinfo[trans_type];
|
|
struct itmon_nodegroup *group = NULL;
|
|
|
|
if (!traceinfo->dirty || traceinfo->trans_dirty)
|
|
return;
|
|
else
|
|
traceinfo->trans_dirty = true;
|
|
|
|
pr_info("--------------------------------------------------------------------------\n"
|
|
" Transaction Information\n\n"
|
|
" > Master : %s %s\n"
|
|
" > Target : %s\n"
|
|
" > Target Address : 0x%zx %s\n"
|
|
" > Type : %s\n"
|
|
" > Error code : %s\n",
|
|
traceinfo->port, traceinfo->master ? traceinfo->master : "",
|
|
traceinfo->dest ? traceinfo->dest : "Unknown",
|
|
traceinfo->target_addr,
|
|
traceinfo->target_addr == INVALID_REMAPPING ?
|
|
"(Violation Access by CP maybe)" : "",
|
|
trans_type == TRANS_TYPE_READ ? "READ" : "WRITE",
|
|
itmon_errcode[traceinfo->errcode]);
|
|
|
|
if (node) {
|
|
struct itmon_tracedata *tracedata = &node->tracedata;
|
|
pr_info(" > Size : %u bytes x %u burst => %u bytes\n"
|
|
" > Burst Type : %u (0:FIXED, 1:INCR, 2:WRAP)\n"
|
|
" > Level : %s\n"
|
|
" > Protection : %s\n",
|
|
power(BIT_AXSIZE(tracedata->ext_info_1), 2), BIT_AXLEN(tracedata->ext_info_1) + 1,
|
|
power(BIT_AXSIZE(tracedata->ext_info_1), 2) * (BIT_AXLEN(tracedata->ext_info_1) + 1),
|
|
BIT_AXBURST(tracedata->ext_info_2),
|
|
(BIT_AXPROT(tracedata->ext_info_2) & 0x1) ? "Privileged access" : "Unprivileged access",
|
|
(BIT_AXPROT(tracedata->ext_info_2) & 0x2) ? "Non-secure access" : "Secure access");
|
|
|
|
group = node->group;
|
|
pr_info(" > Path Type : %s\n"
|
|
"--------------------------------------------------------------------------\n",
|
|
itmon_pathtype[group->bus_type]);
|
|
|
|
/* timeout freezing result */
|
|
if (node->type == S_NODE && node->tmout_frz_enabled &&
|
|
traceinfo->errcode == ERRCODE_TMOUT)
|
|
itmon_report_timeout(itmon, node, trans_type);
|
|
} else {
|
|
pr_info("--------------------------------------------------------------------------\n");
|
|
}
|
|
}
|
|
|
|
static void itmon_report_pathinfo(struct itmon_dev *itmon,
|
|
struct itmon_nodeinfo *node,
|
|
unsigned int trans_type)
|
|
{
|
|
struct itmon_platdata *pdata = itmon->pdata;
|
|
struct itmon_tracedata *tracedata = &node->tracedata;
|
|
struct itmon_traceinfo *traceinfo = &pdata->traceinfo[trans_type];
|
|
|
|
if (!traceinfo->path_dirty) {
|
|
pr_info("--------------------------------------------------------------------------\n"
|
|
" ITMON Report (%s)\n"
|
|
"--------------------------------------------------------------------------\n"
|
|
" PATH Information\n",
|
|
trans_type == TRANS_TYPE_READ ? "READ" : "WRITE");
|
|
traceinfo->path_dirty = true;
|
|
}
|
|
switch (node->type) {
|
|
case M_NODE:
|
|
pr_info(" ||\n"
|
|
" > %14s, %8s(0x%08X)\n",
|
|
node->name, "M_NODE", node->phy_regs + tracedata->offset);
|
|
break;
|
|
case T_S_NODE:
|
|
pr_info(" ||\n"
|
|
" > %14s, %8s(0x%08X)\n",
|
|
node->name, "T_S_NODE", node->phy_regs + tracedata->offset);
|
|
break;
|
|
case T_M_NODE:
|
|
pr_info(" ||\n"
|
|
" > %14s, %8s(0x%08X)\n",
|
|
node->name, "T_M_NODE", node->phy_regs + tracedata->offset);
|
|
break;
|
|
case S_NODE:
|
|
pr_info(" ||\n"
|
|
" > %14s, %8s(0x%08X)\n",
|
|
node->name, "S_NODE", node->phy_regs + tracedata->offset);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void itmon_report_tracedata(struct itmon_dev *itmon,
|
|
struct itmon_nodeinfo *node,
|
|
unsigned int trans_type)
|
|
{
|
|
struct itmon_platdata *pdata = itmon->pdata;
|
|
struct itmon_tracedata *tracedata = &node->tracedata;
|
|
struct itmon_traceinfo *traceinfo = &pdata->traceinfo[trans_type];
|
|
struct itmon_nodegroup *group = node->group;
|
|
struct itmon_masterinfo *master;
|
|
struct itmon_rpathinfo *port;
|
|
unsigned int errcode, axid;
|
|
unsigned long userbit;
|
|
|
|
errcode = BIT_ERR_CODE(tracedata->int_info);
|
|
axid = BIT_AXID(tracedata->int_info);
|
|
userbit = BIT_AXUSER(tracedata->ext_info_2);
|
|
|
|
switch (node->type) {
|
|
case M_NODE:
|
|
/* In this case, we can get information from M_NODE
|
|
* Fill traceinfo->port / target_addr / read / master */
|
|
if (BIT_ERR_VALID(tracedata->int_info) && tracedata->ext_info_2) {
|
|
/* If only detecting M_NODE only */
|
|
if (!strncmp(node->name, "SCI_IRPM", strlen(node->name))) {
|
|
int cluster_num, core_num;
|
|
core_num = axid & GENMASK(1, 0);
|
|
cluster_num = (axid & BIT(2)) >> 2;
|
|
snprintf(traceinfo->buf, SZ_32 - 1, "CPU%d Cluster%d", core_num, cluster_num);
|
|
traceinfo->port = traceinfo->buf;
|
|
} else if (!strncmp(node->name, "CP_PERI_M", strlen(node->name))) {
|
|
snprintf(traceinfo->buf, SZ_32 - 1, "CP");
|
|
traceinfo->port = traceinfo->buf;
|
|
master = (struct itmon_masterinfo *)
|
|
itmon_get_masterinfo(itmon, traceinfo->port, userbit);
|
|
if (master)
|
|
traceinfo->master = master->master_name;
|
|
else
|
|
traceinfo->master = NULL;
|
|
} else {
|
|
traceinfo->port = node->name;
|
|
master = (struct itmon_masterinfo *)
|
|
itmon_get_masterinfo(itmon, node->name, userbit);
|
|
if (master)
|
|
traceinfo->master = master->master_name;
|
|
else
|
|
traceinfo->master = NULL;
|
|
}
|
|
traceinfo->target_addr =
|
|
(unsigned long)(node->tracedata.ext_info_2
|
|
& GENMASK(3, 0) << 32ULL);
|
|
traceinfo->target_addr |= node->tracedata.ext_info_0;
|
|
traceinfo->read = tracedata->read;
|
|
traceinfo->errcode = errcode;
|
|
traceinfo->dirty = true;
|
|
}
|
|
/* If we excepts following M_NODE with S_NODE */
|
|
if (!strncmp(node->name, "SCI_IRPM", strlen(node->name))) {
|
|
set_bit(FROM_CPU, &traceinfo->from);
|
|
} else if (!strncmp(node->name, "SCI_CCM0", strlen(node->name)) ||
|
|
!strncmp(node->name, "SCI_CCM1", strlen(node->name))) {
|
|
set_bit(FROM_CPU_MAY, &traceinfo->from);
|
|
} else if (!strncmp(node->name, "CP_PERI_M", strlen(node->name))) {
|
|
set_bit(FROM_CP, &traceinfo->from);
|
|
} else if (!strncmp(node->name, "BUSC_PERI_M", strlen(node->name))) {
|
|
set_bit(FROM_M_NODE, &traceinfo->from);
|
|
} else {
|
|
/* Pure M_NODE and it doesn't have any information */
|
|
if (!traceinfo->dirty) {
|
|
traceinfo->master = NULL;
|
|
traceinfo->target_addr = 0;
|
|
traceinfo->read = tracedata->read;
|
|
traceinfo->port = node->name;
|
|
traceinfo->errcode = errcode;
|
|
traceinfo->dirty = true;
|
|
}
|
|
}
|
|
itmon_report_pathinfo(itmon, node, trans_type);
|
|
break;
|
|
case S_NODE:
|
|
if (test_bit(FROM_CPU, &traceinfo->from)) {
|
|
/*
|
|
* This case is only for PERI Path
|
|
* Master is CPU cluster
|
|
* user & GENMASK(1, 0) = core number
|
|
*/
|
|
int cluster_num, core_num;
|
|
core_num = axid & GENMASK(1, 0);
|
|
cluster_num = (axid & BIT(2)) >> 2;
|
|
snprintf(traceinfo->buf, SZ_32 - 1, "CPU%d Cluster%d", core_num, cluster_num);
|
|
traceinfo->port = traceinfo->buf;
|
|
} else if (test_bit(FROM_CPU_MAY, &traceinfo->from)) {
|
|
snprintf(traceinfo->buf, SZ_32 - 1, "CPU maybe");
|
|
traceinfo->port = traceinfo->buf;
|
|
} else {
|
|
/*
|
|
* we expect traceinfo->port is always true in this case
|
|
* In DECERR case, the follow information was filled.
|
|
*/
|
|
if (test_bit(FROM_CP, &traceinfo->from)) {
|
|
snprintf(traceinfo->buf, SZ_32 - 1, "CP");
|
|
traceinfo->port = traceinfo->buf;
|
|
}
|
|
if (!traceinfo->port) {
|
|
port = (struct itmon_rpathinfo *)
|
|
itmon_get_rpathinfo(itmon, axid, node->name);
|
|
if (port)
|
|
traceinfo->port = port->port_name;
|
|
else
|
|
traceinfo->port = NULL;
|
|
}
|
|
if (!traceinfo->master) {
|
|
master = (struct itmon_masterinfo *)
|
|
itmon_get_masterinfo(itmon, traceinfo->port,
|
|
userbit & GENMASK(2, 0));
|
|
if (master)
|
|
traceinfo->master = master->master_name;
|
|
else
|
|
traceinfo->master = NULL;
|
|
}
|
|
}
|
|
traceinfo->target_addr =
|
|
(unsigned long)(node->tracedata.ext_info_2
|
|
& GENMASK(3, 0) << 32ULL);
|
|
traceinfo->target_addr |= node->tracedata.ext_info_0;
|
|
traceinfo->errcode = errcode;
|
|
traceinfo->dest = node->name;
|
|
traceinfo->dirty = true;
|
|
itmon_report_pathinfo(itmon, node, trans_type);
|
|
itmon_report_traceinfo(itmon, node, trans_type);
|
|
break;
|
|
case T_S_NODE:
|
|
case T_M_NODE:
|
|
/* Data Path */
|
|
if (!strncmp(group->name, "DATA_CORE", strlen("DATA_CORE"))
|
|
&& node->type == T_M_NODE) {
|
|
/* Exception Situation - DREX PATH */
|
|
traceinfo->dest = "DREX";
|
|
traceinfo->errcode = errcode;
|
|
traceinfo->dirty = true;
|
|
}
|
|
itmon_report_pathinfo(itmon, node, trans_type);
|
|
break;
|
|
default:
|
|
pr_info("Unknown Error - offset:%u\n", tracedata->offset);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void itmon_report_rawdata(struct itmon_dev *itmon, struct itmon_nodeinfo *node)
|
|
{
|
|
struct itmon_tracedata *tracedata = &node->tracedata;
|
|
|
|
pr_info(" > %s(%s, 0x%08X)\n"
|
|
" > REG(0x08~0x18) : 0x%08X, 0x%08X, 0x%08X, 0x%08X\n",
|
|
node->name, itmon_nodestring[node->type],
|
|
node->phy_regs + tracedata->offset,
|
|
tracedata->int_info,
|
|
tracedata->ext_info_0,
|
|
tracedata->ext_info_1,
|
|
tracedata->ext_info_2);
|
|
}
|
|
|
|
static void itmon_route_tracedata(struct itmon_dev *itmon)
|
|
{
|
|
struct itmon_platdata *pdata = itmon->pdata;
|
|
struct itmon_nodeinfo *node, *next_node;
|
|
unsigned int trans_type;
|
|
int i;
|
|
|
|
/* To call function is sorted by declaration */
|
|
for (trans_type = 0; trans_type < TRANS_TYPE_NUM; trans_type++) {
|
|
for (i = M_NODE; i < NODE_TYPE; i++) {
|
|
list_for_each_entry(node, &pdata->tracelist[trans_type], list) {
|
|
if (i == node->type)
|
|
itmon_report_tracedata(itmon, node, trans_type);
|
|
}
|
|
}
|
|
/* If there is no S_NODE and T_S_NODE and T_M_NODE, check one more */
|
|
itmon_report_traceinfo(itmon, NULL, trans_type);
|
|
}
|
|
|
|
if (pdata->traceinfo[TRANS_TYPE_READ].dirty ||
|
|
pdata->traceinfo[TRANS_TYPE_WRITE].dirty)
|
|
pr_info(" Raw Register Information(ITMON Internal Information)\n\n");
|
|
|
|
for (trans_type = 0; trans_type < TRANS_TYPE_NUM; trans_type++) {
|
|
itmon_post_handler_by_master(itmon, node, trans_type);
|
|
for (i = M_NODE; i < NODE_TYPE; i++) {
|
|
list_for_each_entry_safe(node, next_node, &pdata->tracelist[trans_type], list) {
|
|
if (i == node->type) {
|
|
itmon_report_rawdata(itmon, node);
|
|
/* clean up */
|
|
list_del(&node->list);
|
|
kfree(node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pdata->traceinfo[TRANS_TYPE_READ].dirty ||
|
|
pdata->traceinfo[TRANS_TYPE_WRITE].dirty)
|
|
pr_info("--------------------------------------------------------------------------\n");
|
|
}
|
|
|
|
static void itmon_trace_data(struct itmon_dev *itmon,
|
|
struct itmon_nodegroup *group,
|
|
struct itmon_nodeinfo *node,
|
|
unsigned int offset)
|
|
{
|
|
struct itmon_platdata *pdata = itmon->pdata;
|
|
struct itmon_nodeinfo *new_node = NULL;
|
|
unsigned int int_info, info0, info1, info2;
|
|
bool read = TRANS_TYPE_WRITE;
|
|
bool req = false;
|
|
|
|
int_info = __raw_readl(node->regs + offset + REG_INT_INFO);
|
|
info0 = __raw_readl(node->regs + offset + REG_EXT_INFO_0);
|
|
info1 = __raw_readl(node->regs + offset + REG_EXT_INFO_1);
|
|
info2 = __raw_readl(node->regs + offset + REG_EXT_INFO_2);
|
|
|
|
switch (offset) {
|
|
case OFFSET_REQ_R:
|
|
read = TRANS_TYPE_READ;
|
|
/* fall down */
|
|
case OFFSET_REQ_W:
|
|
req = true;
|
|
/* Only S-Node is able to make log to registers */
|
|
break;
|
|
case OFFSET_RESP_R:
|
|
read = TRANS_TYPE_READ;
|
|
/* fall down */
|
|
case OFFSET_RESP_W:
|
|
req = false;
|
|
/* Only NOT S-Node is able to make log to registers */
|
|
break;
|
|
default:
|
|
pr_info("Unknown Error - node:%s offset:%u\n", node->name, offset);
|
|
break;
|
|
}
|
|
|
|
new_node = kmalloc(sizeof(struct itmon_nodeinfo), GFP_ATOMIC);
|
|
if (new_node) {
|
|
/* Fill detected node information to tracedata's list */
|
|
memcpy(new_node, node, sizeof(struct itmon_nodeinfo));
|
|
new_node->tracedata.int_info = int_info;
|
|
new_node->tracedata.ext_info_0 = info0;
|
|
new_node->tracedata.ext_info_1 = info1;
|
|
new_node->tracedata.ext_info_2 = info2;
|
|
new_node->tracedata.offset = offset;
|
|
new_node->tracedata.read = read;
|
|
new_node->group = group;
|
|
if (BIT_ERR_VALID(int_info))
|
|
node->tracedata.logging = true;
|
|
else
|
|
node->tracedata.logging = false;
|
|
|
|
list_add(&new_node->list, &pdata->tracelist[read]);
|
|
} else {
|
|
pr_info("failed to kmalloc for %s node %x offset\n",
|
|
node->name, offset);
|
|
}
|
|
}
|
|
|
|
static int itmon_search_node(struct itmon_dev *itmon, struct itmon_nodegroup *group, bool clear)
|
|
{
|
|
struct itmon_platdata *pdata = itmon->pdata;
|
|
struct itmon_nodeinfo *node = NULL;
|
|
unsigned int val, offset, vec;
|
|
unsigned long flags, bit = 0;
|
|
int i, j, ret = 0;
|
|
|
|
spin_lock_irqsave(&itmon->ctrl_lock, flags);
|
|
memset(pdata->traceinfo, 0, sizeof(struct itmon_traceinfo) * 2);
|
|
if (group) {
|
|
/* Processing only this group and select detected node */
|
|
vec = __raw_readl(group->regs);
|
|
node = group->nodeinfo;
|
|
if (!vec)
|
|
goto exit;
|
|
|
|
for_each_set_bit(bit, (unsigned long *)&vec, group->nodesize) {
|
|
/* exist array */
|
|
for (i = 0; i < OFFSET_NUM; i++) {
|
|
offset = i * OFFSET_ERR_REPT;
|
|
/* Check Request information */
|
|
val = __raw_readl(node[bit].regs + offset + REG_INT_INFO);
|
|
if (BIT_ERR_OCCURRED(val)) {
|
|
/* This node occurs the error */
|
|
itmon_trace_data(itmon, group, &node[bit], offset);
|
|
if (clear)
|
|
__raw_writel(1, node[bit].regs
|
|
+ offset + REG_INT_CLR);
|
|
ret = true;
|
|
}
|
|
}
|
|
|
|
}
|
|
} else {
|
|
/* Processing all group & nodes */
|
|
for (i = 0; i < ARRAY_SIZE(nodegroup); i++) {
|
|
group = &nodegroup[i];
|
|
if (group->phy_regs)
|
|
vec = __raw_readl(group->regs);
|
|
else
|
|
vec = GENMASK(group->nodesize, 0);
|
|
|
|
node = group->nodeinfo;
|
|
bit = 0;
|
|
|
|
for_each_set_bit(bit, (unsigned long *)&vec, group->nodesize) {
|
|
for (j = 0; j < OFFSET_NUM; j++) {
|
|
offset = j * OFFSET_ERR_REPT;
|
|
/* Check Request information */
|
|
val = __raw_readl(node[bit].regs + offset + REG_INT_INFO);
|
|
if (BIT_ERR_OCCURRED(val)) {
|
|
/* This node occurs the error */
|
|
itmon_trace_data(itmon, group, &node[bit], offset);
|
|
if (clear)
|
|
__raw_writel(1, node[bit].regs
|
|
+ offset + REG_INT_CLR);
|
|
ret = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
itmon_route_tracedata(itmon);
|
|
exit:
|
|
spin_unlock_irqrestore(&itmon->ctrl_lock, flags);
|
|
return ret;
|
|
}
|
|
|
|
static irqreturn_t itmon_irq_handler(int irq, void *data)
|
|
{
|
|
struct itmon_dev *itmon = (struct itmon_dev *)data;
|
|
struct itmon_platdata *pdata = itmon->pdata;
|
|
struct itmon_nodegroup *group = NULL;
|
|
bool ret;
|
|
int i;
|
|
|
|
/* Search itmon group */
|
|
for (i = 0; i < ARRAY_SIZE(nodegroup); i++) {
|
|
if (irq == nodegroup[i].irq) {
|
|
group = &pdata->nodegroup[i];
|
|
if (group->phy_regs != 0) {
|
|
pr_info("\nITMON Detected: %d irq, %s group, 0x%x vec, err_cnt:%u\n",
|
|
irq, group->name, __raw_readl(group->regs), pdata->err_cnt);
|
|
} else {
|
|
pr_info("\nITMON Detected: %d irq, %s group, err_cnt:%u\n",
|
|
irq, group->name, pdata->err_cnt);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
ret = itmon_search_node(itmon, NULL, true);
|
|
if (!ret) {
|
|
pr_info("ITMON could not detect any error\n");
|
|
} else {
|
|
if (pdata->err_cnt++ > PANIC_ALLOWED_THRESHOLD)
|
|
pdata->panic_allowed = true;
|
|
}
|
|
|
|
if (pdata->panic_allowed)
|
|
panic("ITMON occurs panic to prevent endless log");
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
void itmon_notifier_chain_register(struct notifier_block *block)
|
|
{
|
|
atomic_notifier_chain_register(&itmon_notifier_list, block);
|
|
}
|
|
|
|
static int itmon_logging_panic_handler(struct notifier_block *nb,
|
|
unsigned long l, void *buf)
|
|
{
|
|
struct itmon_panic_block *itmon_panic = (struct itmon_panic_block *)nb;
|
|
struct itmon_dev *itmon = itmon_panic->pdev;
|
|
int ret;
|
|
|
|
if (!IS_ERR_OR_NULL(itmon)) {
|
|
/* Check error has been logged */
|
|
ret = itmon_search_node(itmon, NULL, false);
|
|
if (!ret)
|
|
pr_info("No found error in %s\n", __func__);
|
|
else
|
|
pr_info("Found errors in %s\n", __func__);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int itmon_probe(struct platform_device *pdev)
|
|
{
|
|
struct itmon_dev *itmon;
|
|
struct itmon_panic_block *itmon_panic = NULL;
|
|
struct itmon_platdata *pdata;
|
|
struct itmon_nodeinfo *node;
|
|
unsigned int irq_option = 0, irq;
|
|
char *dev_name;
|
|
int ret, i, j;
|
|
|
|
itmon = devm_kzalloc(&pdev->dev, sizeof(struct itmon_dev), GFP_KERNEL);
|
|
if (!itmon) {
|
|
dev_err(&pdev->dev, "failed to allocate memory for driver's "
|
|
"private data\n");
|
|
return -ENOMEM;
|
|
}
|
|
itmon->dev = &pdev->dev;
|
|
|
|
spin_lock_init(&itmon->ctrl_lock);
|
|
|
|
pdata = devm_kzalloc(&pdev->dev, sizeof(struct itmon_platdata), GFP_KERNEL);
|
|
if (!pdata) {
|
|
dev_err(&pdev->dev, "failed to allocate memory for driver's "
|
|
"platform data\n");
|
|
return -ENOMEM;
|
|
}
|
|
itmon->pdata = pdata;
|
|
itmon->pdata->masterinfo = masterinfo;
|
|
itmon->pdata->rpathinfo = rpathinfo;
|
|
itmon->pdata->nodegroup = nodegroup;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(nodegroup); i++) {
|
|
dev_name = nodegroup[i].name;
|
|
node = nodegroup[i].nodeinfo;
|
|
|
|
if (nodegroup[i].phy_regs) {
|
|
nodegroup[i].regs = devm_ioremap_nocache(&pdev->dev,
|
|
nodegroup[i].phy_regs, SZ_16K);
|
|
if (nodegroup[i].regs == NULL) {
|
|
dev_err(&pdev->dev, "failed to claim register region - %s\n",
|
|
dev_name);
|
|
return -ENOENT;
|
|
}
|
|
}
|
|
|
|
if (initial_multi_irq_enable)
|
|
irq_option = IRQF_GIC_MULTI_TARGET;
|
|
|
|
irq = irq_of_parse_and_map(pdev->dev.of_node, i);
|
|
nodegroup[i].irq = irq;
|
|
|
|
ret = devm_request_irq(&pdev->dev, irq,
|
|
itmon_irq_handler, irq_option, dev_name, itmon);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "failed to request irq - %s\n", dev_name);
|
|
return -ENOENT;
|
|
} else {
|
|
dev_err(&pdev->dev, "success to register request irq%u - %s\n", irq, dev_name);
|
|
}
|
|
|
|
for (j = 0; j < nodegroup[i].nodesize; j++) {
|
|
node[j].regs = devm_ioremap_nocache(&pdev->dev, node[j].phy_regs, SZ_16K);
|
|
if (node[j].regs == NULL) {
|
|
dev_err(&pdev->dev, "failed to claim register region - %s\n",
|
|
dev_name);
|
|
return -ENOENT;
|
|
}
|
|
}
|
|
}
|
|
|
|
itmon_panic = devm_kzalloc(&pdev->dev, sizeof(struct itmon_panic_block),
|
|
GFP_KERNEL);
|
|
|
|
if (!itmon_panic) {
|
|
dev_err(&pdev->dev, "failed to allocate memory for driver's "
|
|
"panic handler data\n");
|
|
} else {
|
|
itmon_panic->nb_panic_block.notifier_call = itmon_logging_panic_handler;
|
|
itmon_panic->pdev = itmon;
|
|
atomic_notifier_chain_register(&panic_notifier_list,
|
|
&itmon_panic->nb_panic_block);
|
|
}
|
|
|
|
platform_set_drvdata(pdev, itmon);
|
|
|
|
INIT_LIST_HEAD(&pdata->tracelist[BUS_DATA]);
|
|
INIT_LIST_HEAD(&pdata->tracelist[BUS_PERI]);
|
|
|
|
itmon_init(itmon, true);
|
|
|
|
g_itmon = itmon;
|
|
pdata->probed = true;
|
|
|
|
dev_info(&pdev->dev, "success to probe Exynos ITMON driver\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int itmon_remove(struct platform_device *pdev)
|
|
{
|
|
platform_set_drvdata(pdev, NULL);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int itmon_suspend(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int itmon_resume(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct itmon_dev *itmon = platform_get_drvdata(pdev);
|
|
|
|
itmon_init(itmon, true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static SIMPLE_DEV_PM_OPS(itmon_pm_ops, itmon_suspend, itmon_resume);
|
|
#define ITMON_PM (itmon_pm_ops)
|
|
#else
|
|
#define ITM_ONPM NULL
|
|
#endif
|
|
|
|
static struct platform_driver exynos_itmon_driver = {
|
|
.probe = itmon_probe,
|
|
.remove = itmon_remove,
|
|
.driver = {
|
|
.name = "exynos-itmon",
|
|
.of_match_table = itmon_dt_match,
|
|
.pm = &itmon_pm_ops,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(exynos_itmon_driver);
|
|
|
|
MODULE_DESCRIPTION("Samsung Exynos ITMON DRIVER");
|
|
MODULE_AUTHOR("Hosung Kim <hosung0.kim@samsung.com");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("platform:exynos-itmon");
|