blob: be7d70e1020c391381c9c15a2488b55ef3e96561 [file] [log] [blame]
/*
* Copyright (c) 2017 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* IPs Traffic Monitor(ITMON) Driver for Samsung Exynos9810 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 <linux/delay.h>
#include <soc/samsung/exynos-pmu.h>
#include <soc/samsung/exynos-itmon.h>
#include <linux/debug-snapshot.h>
#if defined(CONFIG_SEC_SIPC_MODEM_IF)
#include <soc/samsung/exynos-modem-ctrl.h>
#endif
#ifdef CONFIG_SEC_DEBUG
#include <linux/sec_debug.h>
#endif
#ifdef CONFIG_EXYNOS_ACPM_S2D
#include <soc/samsung/acpm_ipc_ctrl.h>
#endif
//#define MULTI_IRQ_SUPPORT_ITMON
#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_HW_ASSERT (0x100)
#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 (0x20)
#define REG_TMOUT_BUF_STATUS (0x1C)
#define REG_TMOUT_BUF_POINT_ADDR (0x20)
#define REG_TMOUT_BUF_ID (0x24)
#define REG_TMOUT_BUF_PAYLOAD (0x28)
#define REG_TMOUT_BUF_PAYLOAD_SRAM1 (0x30)
#define REG_TMOUT_BUF_PAYLOAD_SRAM2 (0x34)
#define REG_TMOUT_BUF_PAYLOAD_SRAM3 (0x38)
#define REG_HWA_CTL (0x4)
#define REG_HWA_INT (0x8)
#define REG_HWA_INT_ID (0xC)
#define REG_HWA_START_ADDR_LOW (0x10)
#define REG_HWA_END_ADDR_LOW (0x14)
#define REG_HWA_START_END_ADDR_UPPER (0x18)
#define RD_RESP_INT_ENABLE (1 << 0)
#define WR_RESP_INT_ENABLE (1 << 1)
#define ARLEN_RLAST_INT_ENABLE (1 << 2)
#define AWLEN_WLAST_INT_ENABLE (1 << 3)
#define INTEND_ACCESS_INT_ENABLE (1 << 4)
#define BIT_HWA_ERR_OCCURRED(x) (((x) & (0x1 << 0)) >> 0)
#define BIT_HWA_ERR_CODE(x) (((x) & (0xF << 1)) >> 28)
#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 (2)
#define FROM_M_NODE (4)
#define CP_COMMON_STR "CP_"
#define DREX_COMMON_STR "DREX_IRPS"
#define TMOUT (0xFFFFF)
#define TMOUT_TEST (0x1)
#define PANIC_ALLOWED_THRESHOLD (0x2)
#define INVALID_REMAPPING (0x08000000)
/* This value will be fixed */
#define INTEND_ADDR_START (0)
#define INTEND_ADDR_END (0)
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 snode_dirty;
bool dirty;
unsigned long from;
int path_type;
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 dbg_mo_cnt;
unsigned int hwa_ctl;
unsigned int hwa_info;
unsigned int hwa_int_id;
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 hw_assert_enabled;
bool addr_detect_enabled;
bool retention;
struct itmon_tracedata tracedata;
struct itmon_nodegroup *group;
struct list_head list;
};
struct itmon_nodegroup {
int irq;
char *name;
unsigned int phy_regs;
bool ex_table;
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[TRANS_TYPE_NUM];
struct list_head tracelist[TRANS_TYPE_NUM];
unsigned int err_cnt;
bool panic_allowed;
bool crash_in_progress;
unsigned int sysfs_tmout_val;
bool sysfs_scandump;
bool sysfs_s2d;
bool err_fatal;
bool err_hwa;
bool probed;
};
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;
};
const static struct itmon_rpathinfo rpathinfo[] = {
/*
* 0x2000_0000 ~ 0xf_ffff_ffff
*/
{0, "DPU0", "DREX_IRPS", GENMASK(5, 0), 0},
{1, "DPU1", "DREX_IRPS", GENMASK(5, 0), 0},
{2, "DPU2", "DREX_IRPS", GENMASK(5, 0), 0},
{3, "ISPPRE", "DREX_IRPS", GENMASK(5, 0), 0},
{4, "ISPLP0", "DREX_IRPS", GENMASK(5, 0), 0},
{5, "ISPLP1", "DREX_IRPS", GENMASK(5, 0), 0},
{6, "ISPHQ", "DREX_IRPS", GENMASK(5, 0), 0},
{7, "DCRD", "DREX_IRPS", GENMASK(5, 0), 0},
{8, "DCF", "DREX_IRPS", GENMASK(5, 0), 0},
{9, "G2D2", "DREX_IRPS", GENMASK(5, 0), 0},
{10, "FSYS0", "DREX_IRPS", GENMASK(5, 0), 0},
{11, "FSYS1", "DREX_IRPS", GENMASK(5, 0), 0},
{12, "SIREX", "DREX_IRPS", GENMASK(5, 0), 0},
{13, "SBIC", "DREX_IRPS", GENMASK(5, 0), 0},
{14, "PDMA", "DREX_IRPS", GENMASK(5, 0), 0},
{15, "SPDMA", "DREX_IRPS", GENMASK(5, 0), 0},
{16, "BUSC_PD", "DREX_IRPS", GENMASK(5, 0), 0},
{17, "IVA", "DREX_IRPS", GENMASK(5, 0), 0},
{18, "DSPM0", "DREX_IRPS", GENMASK(5, 0), 0},
{19, "DSPM1", "DREX_IRPS", GENMASK(5, 0), 0},
{20, "DSPM2", "DREX_IRPS", GENMASK(5, 0), 0},
{21, "MFC0", "DREX_IRPS", GENMASK(5, 0), 0},
{22, "MFC1", "DREX_IRPS", GENMASK(5, 0), 0},
{23, "G2D0", "DREX_IRPS", GENMASK(5, 0), 0},
{24, "G2D1", "DREX_IRPS", GENMASK(5, 0), 0},
{25, "AUD", "DREX_IRPS", GENMASK(5, 0), 0},
{26, "DPU0", "DREX_IRPS", GENMASK(5, 0), 0},
{27, "DPU1", "DREX_IRPS", GENMASK(5, 0), 0},
{28, "DPU2", "DREX_IRPS", GENMASK(5, 0), 0},
{29, "ISPPRE", "DREX_IRPS", GENMASK(5, 0), 0},
{30, "ISPLP0", "DREX_IRPS", GENMASK(5, 0), 0},
{31, "ISPLP1", "DREX_IRPS", GENMASK(5, 0), 0},
{32, "ISPHQ", "DREX_IRPS", GENMASK(5, 0), 0},
{33, "DCRD", "DREX_IRPS", GENMASK(5, 0), 0},
{34, "DCF", "DREX_IRPS", GENMASK(5, 0), 0},
{35, "G2D2", "DREX_IRPS", GENMASK(5, 0), 0},
{36, "FSYS0", "DREX_IRPS", GENMASK(5, 0), 0},
{37, "FSYS1", "DREX_IRPS", GENMASK(5, 0), 0},
{38, "BUS1_D0", "DREX_IRPS", GENMASK(5, 0), 0},
{39, "SIREX", "DREX_IRPS", GENMASK(5, 0), 0},
{40, "SBIC", "DREX_IRPS", GENMASK(5, 0), 0},
{41, "PDMA", "DREX_IRPS", GENMASK(5, 0), 0},
{42, "SPDMA", "DREX_IRPS", GENMASK(5, 0), 0},
{43, "IVA", "DREX_IRPS", GENMASK(5, 0), 0},
{44, "DSPM0", "DREX_IRPS", GENMASK(5, 0), 0},
{45, "DSPM1", "DREX_IRPS", GENMASK(5, 0), 0},
{46, "DSPM2", "DREX_IRPS", GENMASK(5, 0), 0},
{47, "MFC0", "DREX_IRPS", GENMASK(5, 0), 0},
{48, "MFC1", "DREX_IRPS", GENMASK(5, 0), 0},
{49, "G2D0", "DREX_IRPS", GENMASK(5, 0), 0},
{50, "G2D1", "DREX_IRPS", GENMASK(5, 0), 0},
{51, "AUD", "DREX_IRPS", GENMASK(5, 0), 0},
{52, "G3D0", "DREX_IRPS", GENMASK(5, 0), 0},
{53, "G3D1", "DREX_IRPS", GENMASK(5, 0), 0},
{54, "G3D2", "DREX_IRPS", GENMASK(5, 0), 0},
{55, "G3D3", "DREX_IRPS", GENMASK(5, 0), 0},
{56, "CP_", "DREX_IRPS", GENMASK(5, 0), 0},
{0, "DPU0", "SP", GENMASK(4, 0), 0},
{1, "DPU1", "SP", GENMASK(4, 0), 0},
{2, "DPU2", "SP", GENMASK(4, 0), 0},
{3, "ISPPRE", "SP", GENMASK(4, 0), 0},
{4, "ISPLP0", "SP", GENMASK(4, 0), 0},
{5, "ISPLP1", "SP", GENMASK(4, 0), 0},
{6, "ISPHQ", "SP", GENMASK(4, 0), 0},
{7, "PDMA", "SP", GENMASK(4, 0), 0},
{8, "SPDMA", "SP", GENMASK(4, 0), 0},
{9, "BUSC_PD", "SP", GENMASK(4, 0), 0},
{10, "DCRD", "SP", GENMASK(4, 0), 0},
{11, "DCF", "SP", GENMASK(4, 0), 0},
{12, "IVA", "SP", GENMASK(4, 0), 0},
{13, "DSPM0", "SP", GENMASK(4, 0), 0},
{14, "DSPM1", "SP", GENMASK(4, 0), 0},
{15, "DSPM2", "SP", GENMASK(4, 0), 0},
{16, "MFC0", "SP", GENMASK(4, 0), 0},
{17, "MFC1", "SP", GENMASK(4, 0), 0},
{18, "G2D0", "SP", GENMASK(4, 0), 0},
{19, "G2D1", "SP", GENMASK(4, 0), 0},
{20, "G2D2", "SP", GENMASK(4, 0), 0},
{21, "AUD", "SP", GENMASK(4, 0), 0},
{22, "FSYS0", "SP", GENMASK(4, 0), 0},
{23, "FSYS1", "SP", GENMASK(4, 0), 0},
{24, "BUS1_D0", "SP", GENMASK(4, 0), 0},
{25, "SBIC", "SP", GENMASK(4, 0), 0},
/*
* 0x0000_0000 ~ 0x03ff_ffff
* 0x0410_0000 ~ 0x5fff_ffff
*/
{0, "DPU0", "BUSC_DP0", GENMASK(4, 0), 0},
{1, "DPU1", "BUSC_DP0", GENMASK(4, 0), 0},
{2, "DPU2", "BUSC_DP0", GENMASK(4, 0), 0},
{3, "ISPPRE", "BUSC_DP0", GENMASK(4, 0), 0},
{4, "ISPLP0", "BUSC_DP0", GENMASK(4, 0), 0},
{5, "ISPLP1", "BUSC_DP0", GENMASK(4, 0), 0},
{6, "ISPHQ", "BUSC_DP0", GENMASK(4, 0), 0},
{7, "PDMA", "BUSC_DP0", GENMASK(4, 0), 0},
{8, "SPDMA", "BUSC_DP0", GENMASK(4, 0), 0},
{9, "DCRD", "BUSC_DP0", GENMASK(4, 0), 0},
{10, "DCF", "BUSC_DP0", GENMASK(4, 0), 0},
{11, "IVA", "BUSC_DP0", GENMASK(4, 0), 0},
{12, "DSPM0", "BUSC_DP0", GENMASK(4, 0), 0},
{13, "DSPM1", "BUSC_DP0", GENMASK(4, 0), 0},
{14, "DSPM2", "BUSC_DP0", GENMASK(4, 0), 0},
{15, "MFC0", "BUSC_DP0", GENMASK(4, 0), 0},
{16, "MFC1", "BUSC_DP0", GENMASK(4, 0), 0},
{17, "G2D0", "BUSC_DP0", GENMASK(4, 0), 0},
{18, "G2D1", "BUSC_DP0", GENMASK(4, 0), 0},
{19, "G2D2", "BUSC_DP0", GENMASK(4, 0), 0},
{20, "AUD", "BUSC_DP0", GENMASK(4, 0), 0},
{21, "FSYS0", "BUSC_DP0", GENMASK(4, 0), 0},
{22, "FSYS1", "BUSC_DP0", GENMASK(4, 0), 0},
{23, "BUS1_D0", "BUSC_DP0", GENMASK(4, 0), 0},
{24, "SBIC", "BUSC_DP0", GENMASK(4, 0), 0},
/*
* 0x0600_0000 ~ 0x0fff_ffff
*/
{0, "FSYS0", "BUSC_DP1", GENMASK(2, 0), 0},
{1, "FSYS1", "BUSC_DP1", GENMASK(2, 0), 0},
{2, "BUS1_D0", "BUSC_DP1", GENMASK(2, 0), 0},
{3, "SBIC", "BUSC_DP1", GENMASK(2, 0), 0},
{4, "SPDMA", "BUSC_DP1", GENMASK(2, 0), 0},
/*
* 0x0000_0000 ~ 0x1fff_ffff
*/
{0, "G3D0", "CORE_DP", GENMASK(2, 0), 0},
{1, "G3D1", "CORE_DP", GENMASK(2, 0), 0},
{2, "G3D2", "CORE_DP", GENMASK(2, 0), 0},
{3, "G3D3", "CORE_DP", GENMASK(2, 0), 0},
{4, "CP_", "CORE_DP", GENMASK(2, 0), 0},
};
const static struct itmon_masterinfo masterinfo[] = {
{"DPU0", 0, /* 0XXXX0 */ "DPP", BIT(5) | BIT(0)},
{"DPU0", BIT(0), /* 0XXXX1 */ "SYSMMU_DPU0", BIT(5) | BIT(0)},
{"DPU1", 0, /* 0XXXX0 */ "DPP", BIT(5) | BIT(0)},
{"DPU1", BIT(0), /* 0XXXX1 */ "SYSMMU_DPU1", BIT(5) | BIT(0)},
{"DPU2", 0, /* 0XXXX0 */ "DPP", BIT(5) | BIT(0)},
{"DPU2", BIT(0), /* 0XXXX1 */ "SYSMMU_DPU2", BIT(5) | BIT(0)},
{"DCRD", 0, /* 0XXXX0 */ "DCP", BIT(5) | BIT(0)},
{"DCRD", BIT(0), /* 0XXXX1 */ "SYSMMU_DCRD", BIT(5) | BIT(0)},
{"DCF", 0, /* 0XXX00 */ "CIP1", BIT(5) | GENMASK(1, 0)},
{"DCF", BIT(1), /* 0XXX10 */ "DCPOST_CIP2", BIT(5) | GENMASK(1, 0)},
{"DCF", BIT(0), /* 0XXXX1 */ "SYSMMU_DCF", BIT(5) | BIT(0)},
{"ISPPRE", 0, /* 0XX000 */ "PDP", BIT(5) | GENMASK(2, 0)},
{"ISPPRE", BIT(1), /* 0XX010 */ "TAAM", BIT(5) | GENMASK(2, 0)},
{"ISPPRE", BIT(2), /* 0XX100 */ "TAA", BIT(5) | GENMASK(2, 0)},
{"ISPPRE", BIT(0), /* 0XXXX1 */ "SYSMMU_ISPPRE", BIT(5) | BIT(0)},
{"ISPLP0", 0, /* 0XX000 */ "ISPLP", BIT(5) | GENMASK(2, 0)},
{"ISPLP0", BIT(1), /* 0XX010 */ "GDC", BIT(5) | GENMASK(2, 0)},
{"ISPLP0", BIT(2), /* 0XX100 */ "SRDZ", BIT(5) | GENMASK(2, 0)},
{"ISPLP0", GENMASK(2, 1), /* 0XX110 */ "VRA", BIT(5) | GENMASK(2, 0)},
{"ISPLP0", BIT(0), /* 0XXXX1 */ "SYSMMU_ISPLP0", BIT(5) | BIT(0)},
{"ISPLP1", 0, /* 0XXXX0 */ "MC_SCALER", BIT(5) | BIT(0)},
{"ISPLP1", BIT(0), /* 0XXXX1 */ "SYSMMU_ISPLP1", BIT(5) | BIT(0)},
{"ISPHQ", 0, /* 0XXXX0 */ "ISPHQ", BIT(5) | BIT(0)},
{"ISPHQ", BIT(0), /* 0XXXX1 */ "SYSMMU_ISPHQ", BIT(5) | BIT(0)},
{"IVA", 0, /* 0XXXX0 */ "IVA", BIT(5) | BIT(0)},
{"IVA", BIT(0), /* 0XXXX1 */ "SYSMMU_IVA", BIT(5) | BIT(0)},
{"DSPM0", 0, /* 0XXXX0 */ "SCOREAP2", BIT(5) | BIT(0)},
{"DSPM0", BIT(0), /* 0XXXX1 */ "SYSMMU_DSPM0", BIT(5) | BIT(0)},
{"DSPM1", 0, /* 0XXXX0 */ "SCOREAP2", BIT(5) | BIT(0)},
{"DSPM1", BIT(0), /* 0XXXX1 */ "SYSMMU_DSPM1", BIT(5) | BIT(0)},
{"DSPM2", 0, /* 0XXXX0 */ "CNN", BIT(5) | BIT(0)},
{"DSPM2", BIT(0), /* 0XXXX1 */ "SYSMMU_DSPM2", BIT(5) | BIT(0)},
{"MFC0", 0, /* 0XXXX0 */ "MFC0", BIT(5) | BIT(0)},
{"MFC0", BIT(0), /* 0XXXX1 */ "SYSMMU_MFC0", BIT(5) | BIT(0)},
{"MFC1", 0, /* 0XXX00 */ "MFC1", BIT(5) | GENMASK(1, 0)},
{"MFC1", BIT(1), /* 0XXX10 */ "WFD", BIT(5) | GENMASK(1, 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, /* 0XX000 */ "JPEG", BIT(5) | GENMASK(2, 0)},
{"G2D2", BIT(1), /* 0XX010 */ "MSCL", BIT(5) | GENMASK(2, 0)},
{"G2D2", BIT(2), /* 0XX100 */ "ASTC", BIT(5) | GENMASK(2, 0)},
{"G2D2", BIT(0), /* 0XXXX1 */ "SYSMMU_G2D2", BIT(5) | BIT(0)},
{"AUD", 0, /* 0XX000 */ "SPUS/SPUM", BIT(5) | GENMASK(2, 0)},
{"AUD", BIT(1), /* 0XX010 */ "Cortex-A7", BIT(5) | GENMASK(2, 0)},
{"AUD", BIT(2), /* 0XX100 */ "P_AUD", BIT(5) | GENMASK(2, 0)},
{"AUD", BIT(0), /* 0XXXX1 */ "SYSMMU_ABOX", BIT(5) | GENMASK(2, 0)},
{"FSYS0", 0, /* 0XXX00 */ "ETR", BIT(5) | GENMASK(1, 0)},
{"FSYS0", BIT(1), /* 0XXX10 */ "USB30DRD", BIT(5) | GENMASK(1, 0)},
{"FSYS0", BIT(0), /* 0XXXX1 */ "UFS_EMBD", BIT(5) | BIT(0)},
{"FSYS1", 0, /* 0XX000 */ "MMC_CARD", BIT(5) | GENMASK(2, 0)},
{"FSYS1", BIT(0), /* 0X0001 */ "PCIE_GEN2", BIT(5) | GENMASK(3, 0)},
{"FSYS1", BIT(3) | BIT(0), /* 0X1001 */ "SYSMMU_FSYS1(PCIE_GEN2)", BIT(5) | GENMASK(3, 0)},
{"FSYS1", BIT(1), /* 0X0010 */ "PCIE_GEN3", BIT(5) | GENMASK(3, 0)},
{"FSYS1", BIT(3) | BIT(1), /* 0X1010 */ "SYSMMU_FSYS1(PCIE_GEN3)", BIT(5) | GENMASK(3, 0)},
{"FSYS1", BIT(2), /* 0XX100 */ "SSS", BIT(5) | GENMASK(2, 0)},
{"FSYS1", BIT(2) | BIT(0), /* 0XX101 */ "RTIC", BIT(5) | GENMASK(2, 0)},
{"FSYS1", GENMASK(2, 1), /* 0XX110 */ "UFS_CARD", BIT(5) | GENMASK(2, 0)},
{"BUS1_D0", 0, /* 000000 */ "ALIVE-APM", GENMASK(5, 0)},
{"BUS1_D0", BIT(2), /* 000100 */ "ALIVE-PEM", GENMASK(5, 0)},
{"BUS1_D0", BIT(3), /* 001000 */ "ALIVE-P_APM", GENMASK(5, 0)},
{"BUS1_D0", GENMASK(3, 2), /* 001100 */ "ALIVE-P_APM_CHUB", GENMASK(5, 0)},
{"BUS1_D0", BIT(4), /* 010000 */ "ALIVE-P_APM_GNSS", GENMASK(5, 0)},
{"BUS1_D0", BIT(4) | BIT(2), /* 010100 */ "ALIVE-P_APM_CP", GENMASK(5, 0)},
{"BUS1_D0", BIT(0), /* 000001 */ "GNSS-CM7F_0", GENMASK(5, 0)},
{"BUS1_D0", BIT(2) | BIT(0), /* 000101 */ "GNSS-CM7F_1", GENMASK(5, 0)},
{"BUS1_D0", BIT(3) | BIT(0), /* 001001 */ "GNSS-XDMA0", GENMASK(5, 0)},
{"BUS1_D0", GENMASK(3, 2) | BIT(0),/* 001101 */ "GNSS-XDMA1", GENMASK(5, 0)},
{"BUS1_D0", BIT(4) | BIT(0), /* 010001 */ "GNSS-P_PERIS", GENMASK(5, 0)},
{"BUS1_D0", BIT(1), /* 000010 */ "CHUB-BUSMATRIX_CHUB", GENMASK(5, 0)},
{"BUS1_D0", GENMASK(2, 1), /* 000110 */ "CHUB-CM4_CHUB", GENMASK(5, 0)},
{"BUS1_D0", BIT(3) | BIT(1), /* 001010 */ "CHUB-PDMA_CHUB", GENMASK(5, 0)},
{"BUS1_D0", GENMASK(3, 1), /* 001110 */ "CHUB-P_CHUB", GENMASK(5, 0)},
{"BUS1_D0", BIT(4) | BIT(1), /* 010010 */ "CHUB-SS_VTS", GENMASK(5, 0)},
{"BUS1_D0", BIT(4) | GENMASK(2, 1),/* 010110 */ "CHUB-LP_CHUB", GENMASK(5, 0)},
{"BUS1_D0", GENMASK(1, 0), /* 0XXX11 */ "CSSYS", GENMASK(5, 0)},
{"CP_", 0, /* 000000 */ "UCPU", GENMASK(5, 0)},
{"CP_", BIT(0), /* 000001 */ "LCPU", GENMASK(5, 0)},
{"CP_", BIT(1), /* 000010 */ "DMA0", GENMASK(5, 0)},
{"CP_", GENMASK(1, 0), /* 000011 */ "DMA1", GENMASK(5, 0)},
{"CP_", BIT(2), /* 000100 */ "DMA2", GENMASK(5, 0)},
{"CP_", BIT(2) | BIT(0), /* 000101 */ "DATAMOVER", GENMASK(5, 0)},
{"CP_", GENMASK(2, 1), /* 000110 */ "BAYES", GENMASK(5, 0)},
{"CP_", GENMASK(2, 0), /* 000111 */ "LOGGER", GENMASK(5, 0)},
{"CP_", BIT(3), /* 001000 */ "LMAC0", GENMASK(5, 0)},
{"CP_", BIT(3) | BIT(0), /* 001001 */ "LMAC1", GENMASK(5, 0)},
{"CP_", BIT(3) | BIT(1), /* 001010 */ "DIT", GENMASK(5, 0)},
{"CP_", BIT(3) | GENMASK(1, 0), /* 001011 */ "HARQMOVER0", GENMASK(5, 0)},
{"CP_", GENMASK(3, 2), /* 001100 */ "CSXAP", GENMASK(5, 0)},
{"CP_", GENMASK(3, 2) | BIT(0), /* 001101 */ "HARQMOVER1", GENMASK(5, 0)},
/* Others */
{"SIREX", 0, "SIREX", 0},
{"SBIC", 0, "SBIC", 0},
{"PDMA", 0, "PDMA", 0},
{"SPDMA", 0, "SPDMA", 0},
{"BUSC_PD", 0, "BUSC_PD", 0},
};
static struct itmon_nodeinfo data_bus_c[] = {
/* Vector table 1 */
{M_NODE, "AUD", 0x1B123000, NULL, 0, false, false, true, true, false},
{M_NODE, "BUS1_D0", 0x1B153000, NULL, 0, false, false, true, true, false},
{M_NODE, "BUSC_PD", 0x1B1A3000, NULL, 0, false, false, true, true, false},
{M_NODE, "DCF", 0x1B083000, NULL, 0, false, false, true, true, false},
{M_NODE, "DCRD", 0x1B073000, NULL, 0, false, false, true, true, false},
{M_NODE, "DPU0", 0x1B003000, NULL, 0, false, false, true, true, false},
{M_NODE, "DPU1", 0x1B013000, NULL, 0, false, false, true, true, false},
{M_NODE, "DPU2", 0x1B023000, NULL, 0, false, false, true, true, false},
{M_NODE, "DSPM0", 0x1B0A3000, NULL, 0, false, false, true, true, false},
{M_NODE, "DSPM1", 0x1B0B3000, NULL, 0, false, false, true, true, false},
{M_NODE, "DSPM2", 0x1B0C3000, NULL, 0, false, false, true, true, false},
{M_NODE, "FSYS0", 0x1B133000, NULL, 0, false, false, true, true, false},
{M_NODE, "FSYS1", 0x1B143000, NULL, 0, false, false, true, true, false},
{M_NODE, "G2D0", 0x1B0F3000, NULL, 0, false, false, true, true, false},
{M_NODE, "G2D1", 0x1B103000, NULL, 0, false, false, true, true, false},
{M_NODE, "G2D2", 0x1B113000, NULL, 0, false, false, true, true, false},
{M_NODE, "ISPHQ", 0x1B063000, NULL, 0, false, false, true, true, false},
{M_NODE, "ISPLP0", 0x1B043000, NULL, 0, false, false, true, true, false},
{M_NODE, "ISPLP1", 0x1B053000, NULL, 0, false, false, true, true, false},
{M_NODE, "ISPPRE", 0x1B033000, NULL, 0, false, false, true, true, false},
{M_NODE, "IVA", 0x1B093000, NULL, 0, false, false, true, true, false},
{M_NODE, "MFC0", 0x1B0D3000, NULL, 0, false, false, true, true, false},
{M_NODE, "MFC1", 0x1B0E3000, NULL, 0, false, false, true, true, false},
{M_NODE, "PDMA", 0x1B183000, NULL, 0, false, false, true, true, false},
{M_NODE, "SBIC", 0x1B173000, NULL, 0, false, false, true, true, false},
{M_NODE, "SIREX", 0x1B163000, NULL, 0, false, false, true, true, false},
{M_NODE, "SPDMA", 0x1B193000, NULL, 0, false, false, true, true, false},
{S_NODE, "BUSC_DP0", 0x1B203000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "BUSC_DP1", 0x1B213000, NULL, TMOUT, true, false, true, true, false},
{T_S_NODE, "BUSC_M0", 0x1B1B3000, NULL, 0, false, false, true, true, false},
{T_S_NODE, "BUSC_M1", 0x1B1C3000, NULL, 0, false, false, true, true, false},
{T_S_NODE, "BUSC_M2", 0x1B1D3000, NULL, 0, false, false, true, true, false},
/* Vector table 2 start - evt1 */
{T_S_NODE, "BUSC_M3", 0x1B1E3000, NULL, 0, false, false, true, true, false},
/* Vector table 2 start - evt0 */
{S_NODE, "SP", 0x1B1F3000, NULL, TMOUT, true, false, true, true, false},
};
static struct itmon_nodeinfo data_core[] = {
{T_M_NODE, "BUSC_M0", 0x1A803000, NULL, 0, false, false, true, true, false},
{T_M_NODE, "BUSC_M1", 0x1A813000, NULL, 0, false, false, true, true, false},
{T_M_NODE, "BUSC_M2", 0x1A823000, NULL, 0, false, false, true, true, false},
{T_M_NODE, "BUSC_M3", 0x1A833000, NULL, 0, false, false, true, true, false},
{M_NODE, "CP_", 0x1A883000, NULL, 0, false, false, true, true, false},
{M_NODE, "G3D0", 0x1A843000, NULL, 0, false, false, true, true, false},
{M_NODE, "G3D1", 0x1A853000, NULL, 0, false, false, true, true, false},
{M_NODE, "G3D2", 0x1A863000, NULL, 0, false, false, true, true, false},
{M_NODE, "G3D3", 0x1A873000, NULL, 0, false, false, true, true, false},
{S_NODE, "CORE_DP", 0x1A8B3000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "DREX_IRPS0", 0x1A893000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "DREX_IRPS1", 0x1A8A3000, NULL, TMOUT, true, false, true, true, false},
};
static struct itmon_nodeinfo peri_core_0[] = {
{M_NODE, "CLUSTER0", 0x1AA63000, NULL, 0, false, false, true, true, false},
{M_NODE, "CORE_DP", 0x1AA53000, NULL, 0, false, false, true, true, false},
{M_NODE, "SCI_CCM0", 0x1AA23000, NULL, 0, false, false, true, true, false},
{M_NODE, "SCI_CCM1", 0x1AA33000, NULL, 0, false, false, true, true, false},
{M_NODE, "SCI_IRPM", 0x1AA43000, NULL, 0, false, false, true, true, false},
{T_S_NODE, "CORE_BUSC", 0x1AA13000, NULL, 0, false, false, true, true, false},
{T_S_NODE, "CORE_POP1", 0x1AA03000, NULL, 0, false, false, true, true, false},
};
static struct itmon_nodeinfo peri_core_1[] = {
{T_M_NODE, "BUSC_CORE", 0x1AC13000, NULL, 0, false, false, true, true, false},
{T_M_NODE, "CORE_POP1", 0x1AC03000, NULL, 0, false, false, true, true, false},
{S_NODE, "ALIVE", 0x1AC83000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "CP_PERI", 0x1AC73000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "CPUCL0", 0x1AC43000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "CPUCL1", 0x1AC53000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "G3D", 0x1AC63000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "SFR_CORE", 0x1AC33000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "TREX_DP_CORE", 0x1AC23000, NULL, TMOUT, true, false, true, true, false},
};
static struct itmon_nodeinfo peri_bus_c[] = {
{M_NODE, "BUSC_DP0", 0x1B5A3000, NULL, 0, false, false, true, true, false},
{M_NODE, "BUSC_DP1", 0x1B5B3000, NULL, 0, false, false, true, true, false},
{T_M_NODE, "CORE_BUSC", 0x1B5C3000, NULL, 0, false, false, true, true, false},
{S_NODE, "ABOX", 0x1B553000, NULL, TMOUT, true, false, true, true, false},
{T_S_NODE, "BUSC_BUS1", 0x1B433000, NULL, 0, false, false, true, true, false},
{T_S_NODE, "BUSC_CORE", 0x1B423000, NULL, 0, false, false, true, true, false},
{S_NODE, "BUSC_PD", 0x1B593000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "DCF", 0x1B503000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "DCRD", 0x1B4F3000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "DPU", 0x1B4B3000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "DSPM", 0x1B523000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "FSYS0", 0x1B563000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "FSYS1", 0x1B573000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "G2D", 0x1B543000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "ISPHQ", 0x1B4E3000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "ISPLP", 0x1B4D3000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "ISPPRE", 0x1B4C3000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "IVA", 0x1B513000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "MFC", 0x1B533000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "MIF0", 0x1B443000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "MIF1", 0x1B453000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "MIF2", 0x1B463000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "MIF3", 0x1B473000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "PERIC0", 0x1B493000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "PERIC1", 0x1B4A3000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "PERICS", 0x1B483000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "SFR_BUSC", 0x1B413000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "SIREX", 0x1B583000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "TREX_DP_BUSC", 0x1B403000, NULL, TMOUT, true, false, true, true, false},
};
static struct itmon_nodeinfo peri_bus_1[] = {
{T_M_NODE, "BUSC_BUS1", 0x1B653000, NULL, 0, false, false, true, true, false},
{S_NODE, "CHUB", 0x1B633000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "CORESIGHT", 0x1B643000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "GNSS", 0x1B623000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "SFR_BUS1", 0x1B613000, NULL, TMOUT, true, false, true, true, false},
{S_NODE, "TREX_DP_BUS1", 0x1B603000, NULL, TMOUT, true, false, true, true, false},
};
static struct itmon_nodegroup nodegroup[] = {
{82, "DATA_BUS_C", 0x1B3F3000, true, NULL, data_bus_c, ARRAY_SIZE(data_bus_c), BUS_DATA},
{122, "DATA_CORE", 0x1A8f3000, false, NULL, data_core, ARRAY_SIZE(data_core), BUS_DATA},
{126, "PERI_CORE_0", 0x1AAF3000, false, NULL, peri_core_0, ARRAY_SIZE(peri_core_0), BUS_PERI},
{83, "PERI_BUS_C", 0x1B5F3000, false, NULL, peri_bus_c, ARRAY_SIZE(peri_bus_c), BUS_PERI},
{127, "PERI_CORE_1", 0x1ACF3000, false, NULL, peri_core_1, ARRAY_SIZE(peri_core_1), BUS_PERI},
{81, "PERI_BUS_1", 0x1B6F3000, false, NULL, peri_bus_1, ARRAY_SIZE(peri_bus_1), BUS_PERI},
};
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",
};
static struct itmon_dev *g_itmon;
/* 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);
#define EXYNOS_PMU_BURNIN_CTRL 0x0A08
#define BIT_ENABLE_DBGSEL_WDTRESET BIT(25)
#ifdef CONFIG_S3C2410_WATCHDOG
extern int s3c2410wdt_set_emergency_reset(unsigned int timeout, int index);
#else
#define s3c2410wdt_set_emergency_reset(a, b) do { } while(0)
#endif
static void itmon_switch_scandump(struct itmon_dev *itmon)
{
unsigned int val;
int ret;
struct itmon_platdata *pdata = itmon->pdata;
if (pdata->err_fatal || pdata->err_hwa) {
pr_info("%s: ITMON to SCAN2DRAM\n", __func__);
ret = exynos_pmu_read(EXYNOS_PMU_BURNIN_CTRL, &val);
ret = exynos_pmu_write(EXYNOS_PMU_BURNIN_CTRL, val | BIT_ENABLE_DBGSEL_WDTRESET);
s3c2410wdt_set_emergency_reset(3, 0);
exynos_ss_spin_func();
}
}
#ifdef CONFIG_EXYNOS_ACPM_S2D
static void __itmon_switch_s2d(void)
{
s3c2410wdt_set_emergency_reset(3, 0);
exynos_ss_spin_func();
}
#endif
static void itmon_switch_s2d(struct itmon_dev *itmon)
{
#ifdef CONFIG_EXYNOS_ACPM_S2D
struct itmon_platdata *pdata = itmon->pdata;
if (pdata->err_fatal || pdata->err_hwa) {
pr_info("%s: ITMON to SCAN2DRAM\n", __func__);
__itmon_switch_s2d();
}
#endif
}
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;
if (!dest_name)
return NULL;
for (i = 0; i < (int)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;
if (!port_name)
return NULL;
for (i = 0; i < (int)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 < (int)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 ITMON - %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 ITMON - %s error reporting enabled\n", node[j].name);
}
if (node[j].hw_assert_enabled) {
offset = OFFSET_HW_ASSERT;
__raw_writel(RD_RESP_INT_ENABLE | WR_RESP_INT_ENABLE |
ARLEN_RLAST_INT_ENABLE | AWLEN_WLAST_INT_ENABLE,
node[j].regs + offset + REG_HWA_CTL);
}
if (node[j].addr_detect_enabled) {
/* This feature is only for M_NODE */
unsigned int tmp, val;
offset = OFFSET_HW_ASSERT;
val = __raw_readl(node[j].regs + offset + REG_HWA_CTL);
val |= INTEND_ACCESS_INT_ENABLE;
__raw_writel(val, node[j].regs + offset + REG_HWA_CTL);
val = ((unsigned int)INTEND_ADDR_START & 0xFFFFFFFF);
__raw_writel(val, node[j].regs + offset + REG_HWA_START_ADDR_LOW);
val = (unsigned int)(((unsigned long)INTEND_ADDR_START >> 32) & 0XFFFF);
__raw_writel(val, node[j].regs + offset + REG_HWA_START_END_ADDR_UPPER);
val = ((unsigned int)INTEND_ADDR_END & 0xFFFFFFFF);
__raw_writel(val, node[j].regs + offset + REG_HWA_END_ADDR_LOW);
val = ((unsigned int)(((unsigned long)INTEND_ADDR_END >> 32) & 0XFFFF0000) << 16);
tmp = readl(node[j].regs + offset + REG_HWA_START_END_ADDR_UPPER);
__raw_writel(tmp | val, node[j].regs + offset + REG_HWA_START_END_ADDR_UPPER);
}
}
}
}
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_to_notifier(struct itmon_dev *itmon,
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;
itmon->notifier_info.port = traceinfo->port;
itmon->notifier_info.master = traceinfo->master;
itmon->notifier_info.dest = traceinfo->dest;
itmon->notifier_info.read = traceinfo->read;
itmon->notifier_info.target_addr = traceinfo->target_addr;
itmon->notifier_info.errcode = traceinfo->errcode;
/* call notifier_call_chain of itmon */
atomic_notifier_call_chain(&itmon_notifier_list, 0, &itmon->notifier_info);
}
static void itmon_post_handler_by_master(struct itmon_dev *itmon,
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, "CPU", strlen("CPU"))) ||
(!strncmp(traceinfo->port, "SCI_CCM", strlen("SCI_CCM")))) {
/* if master is CPU or SCI_CCM, 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");
}
} else if (!strncmp(traceinfo->port, CP_COMMON_STR, strlen(CP_COMMON_STR))) {
/* if master is DSP and operation is read, we don't care this */
if (traceinfo->master && traceinfo->target_addr == INVALID_REMAPPING &&
!strncmp(traceinfo->master, "LCPU", 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);
#if defined(CONFIG_SEC_SIPC_MODEM_IF)
pdata->crash_in_progress = true;
modem_force_crash_exit_ext();
#endif
}
}
}
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, info;
unsigned long addr;
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 rw_offset = (trans_type == TRANS_TYPE_READ ? 0 : REG_TMOUT_BUF_WR_OFFSET);
pr_info("\n----------------------------------------------------------------------------------\n"
" ITMON Report (%s)\n"
"----------------------------------------------------------------------------------\n"
" Timeout Error Occurred : Master --> %s (DRAM)\n\n",
trans_type == TRANS_TYPE_READ ? "READ" : "WRITE", node->name);
pr_info(" TIMEOUT_BUFFER Information(NODE: %s)\n"
" > NUM| BLOCK| MASTER|VALID|TIMEOUT| ID| PAYLOAD| ADDRESS| SRAM3|\n",
node->name);
for (i = 0; i < num; i++) {
writel(i, node->regs + OFFSET_TMOUT_REG +
REG_TMOUT_BUF_POINT_ADDR + rw_offset);
id = readl(node->regs + OFFSET_TMOUT_REG +
REG_TMOUT_BUF_ID + rw_offset);
payload = readl(node->regs + OFFSET_TMOUT_REG +
REG_TMOUT_BUF_PAYLOAD + rw_offset);
addr = (((unsigned long)readl(node->regs + OFFSET_TMOUT_REG +
REG_TMOUT_BUF_PAYLOAD_SRAM1 + rw_offset) &
GENMASK(15, 0)) << 32ULL);
addr |= (readl(node->regs + OFFSET_TMOUT_REG +
REG_TMOUT_BUF_PAYLOAD_SRAM2 + rw_offset));
info = readl(node->regs + OFFSET_TMOUT_REG +
REG_TMOUT_BUF_PAYLOAD_SRAM3 + rw_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 & (unsigned int)(GENMASK(19, 16))) >> 16;
port = (struct itmon_rpathinfo *)
itmon_get_rpathinfo(itmon, axid, node->name);
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|%5u|%7x|%08x|%08X|%010zx|%08x|\n",
i, port_name, master_name, valid, timeout, id, payload, addr, info);
}
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)
return;
pr_auto(ASL3,
"--------------------------------------------------------------------------\n"
" Transaction Information\n\n"
" > Master : %s %s\n"
" > Target : %s\n"
" > Target Address : 0x%lX %s\n"
" > Type : %s\n"
" > Error code : %s\n",
traceinfo->port, traceinfo->master ? traceinfo->master : "",
traceinfo->dest ? traceinfo->dest : "Unknown",
traceinfo->target_addr,
(unsigned int)traceinfo->target_addr == INVALID_REMAPPING ?
"(BAAW Remapped address)" : "",
trans_type == TRANS_TYPE_READ ? "READ" : "WRITE",
itmon_errcode[traceinfo->errcode]);
if (node) {
struct itmon_tracedata *tracedata = &node->tracedata;
pr_auto(ASL3,
" > 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_auto(ASL3,
" > Path Type : %s\n"
"--------------------------------------------------------------------------\n",
itmon_pathtype[traceinfo->path_type == -1 ? group->bus_type : traceinfo->path_type]);
} else {
pr_auto(ASL3, "--------------------------------------------------------------------------\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_auto(ASL3,
"--------------------------------------------------------------------------\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_auto(ASL3, " > %14s, %8s(0x%08X)\n",
node->name, "M_NODE", node->phy_regs + tracedata->offset);
break;
case T_S_NODE:
pr_auto(ASL3, " > %14s, %8s(0x%08X)\n",
node->name, "T_S_NODE", node->phy_regs + tracedata->offset);
break;
case T_M_NODE:
pr_auto(ASL3, " > %14s, %8s(0x%08X)\n",
node->name, "T_M_NODE", node->phy_regs + tracedata->offset);
break;
case S_NODE:
pr_auto(ASL3, " > %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_masterinfo *master;
struct itmon_rpathinfo *port;
unsigned int errcode, axid;
unsigned int userbit;
bool port_find;
errcode = BIT_ERR_CODE(tracedata->int_info);
axid = (unsigned int)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 */
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;
if (!strncmp(node->name, CP_COMMON_STR, strlen(CP_COMMON_STR)))
set_bit(FROM_CP, &traceinfo->from);
traceinfo->target_addr = (((unsigned long)node->tracedata.ext_info_1
& GENMASK(3, 0)) << 32ULL);
traceinfo->target_addr |= node->tracedata.ext_info_0;
traceinfo->read = tracedata->read;
traceinfo->errcode = errcode;
traceinfo->dirty = true;
} else {
if (!strncmp(node->name, "SCI_IRPM", strlen(node->name)))
set_bit(FROM_CPU, &traceinfo->from);
}
/* 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 slave error
* Master is CPU cluster
* user & GENMASK(1, 0) = core number
*/
int cluster_num, core_num;
core_num = userbit & GENMASK(1, 0);
cluster_num = (userbit & 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_CP, &traceinfo->from) && (!traceinfo->port))
traceinfo->port = CP_COMMON_STR;
/* S_NODE = BUSC_DP or CORE_DP => PERI */
if (!strncmp(node->name, "BUSC_DP", strlen(node->name)) ||
!strncmp(node->name, "CORE_DP", strlen(node->name))) {
port_find = true;
traceinfo->path_type = BUS_PERI;
} else if (!strncmp(node->name, DREX_COMMON_STR, strlen(DREX_COMMON_STR))) {
port_find = true;
traceinfo->path_type = BUS_DATA;
} else {
port_find = false;
traceinfo->path_type = -1;
}
if (port_find && !test_bit(FROM_CPU, &traceinfo->from)) {
port = (struct itmon_rpathinfo *)
itmon_get_rpathinfo(itmon, axid, node->name);
/* If it couldn't find port, keep previous information */
if (port) {
traceinfo->port = port->port_name;
master = (struct itmon_masterinfo *)
itmon_get_masterinfo(itmon, traceinfo->port,
userbit);
if (master)
traceinfo->master = master->master_name;
}
} else {
/* If it has traceinfo->port, keep previous information */
if (!traceinfo->port) {
port = (struct itmon_rpathinfo *)
itmon_get_rpathinfo(itmon, axid, node->name);
if (port)
traceinfo->port = port->port_name;
}
if (!traceinfo->master && traceinfo->port) {
master = (struct itmon_masterinfo *)
itmon_get_masterinfo(itmon, traceinfo->port,
userbit);
if (master)
traceinfo->master = master->master_name;
}
}
/* Update targetinfo with S_NODE */
traceinfo->target_addr =
(((unsigned long)node->tracedata.ext_info_1
& GENMASK(3, 0)) << 32ULL);
traceinfo->target_addr |= node->tracedata.ext_info_0;
traceinfo->errcode = errcode;
traceinfo->dest = node->name;
traceinfo->dirty = true;
traceinfo->snode_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:
itmon_report_pathinfo(itmon, node, trans_type);
break;
default:
pr_info("Unknown Error - offset:%u\n", tracedata->offset);
break;
}
}
static void itmon_report_hwa_rawdata(struct itmon_dev *itmon,
struct itmon_nodeinfo *node)
{
unsigned int dbg_mo_cnt, hwa_ctl, hwa_info, hwa_int_id;
dbg_mo_cnt = __raw_readl(node->regs + OFFSET_HW_ASSERT);
hwa_ctl = __raw_readl(node->regs + OFFSET_HW_ASSERT + REG_HWA_CTL);
hwa_info = __raw_readl(node->regs + OFFSET_HW_ASSERT + REG_HWA_INT);
hwa_int_id = __raw_readl(node->regs + OFFSET_HW_ASSERT + REG_HWA_INT_ID);
/* Output Raw register information */
pr_info("--------------------------------------------------------------------------\n"
" HWA Raw Register Information(ITMON information)\n\n");
pr_info(" > %s(%s, 0x%08X)\n"
" > REG(0x100~0x10C) : 0x%08X, 0x%08X, 0x%08X, 0x%08X\n",
node->name, itmon_nodestring[node->type],
node->phy_regs,
dbg_mo_cnt,
hwa_ctl,
hwa_info,
hwa_int_id);
}
static void itmon_report_rawdata(struct itmon_dev *itmon,
struct itmon_nodeinfo *node,
unsigned int trans_type)
{
struct itmon_tracedata *tracedata = &node->tracedata;
/* Output Raw register information */
pr_info(" > %s(%s, 0x%08X)\n"
" > REG(0x08~0x18) : 0x%08X, 0x%08X, 0x%08X, 0x%08X\n"
" > REG(0x100~0x10C) : 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,
tracedata->dbg_mo_cnt,
tracedata->hwa_ctl,
tracedata->hwa_info,
tracedata->hwa_int_id);
}
static void itmon_route_tracedata(struct itmon_dev *itmon)
{
struct itmon_platdata *pdata = itmon->pdata;
struct itmon_traceinfo *traceinfo;
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 information, check one more */
traceinfo = &pdata->traceinfo[trans_type];
if (!traceinfo->snode_dirty)
itmon_report_traceinfo(itmon, NULL, trans_type);
}
if (pdata->traceinfo[TRANS_TYPE_READ].dirty ||
pdata->traceinfo[TRANS_TYPE_WRITE].dirty)
pr_auto(ASL3, " Raw Register Information(ITMON Internal Information)\n\n");
for (trans_type = 0; trans_type < TRANS_TYPE_NUM; 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, trans_type);
/* clean up */
list_del(&node->list);
kfree(node);
}
}
}
}
if (pdata->traceinfo[TRANS_TYPE_READ].dirty ||
pdata->traceinfo[TRANS_TYPE_WRITE].dirty)
pr_auto(ASL3, "--------------------------------------------------------------------------\n");
for (trans_type = 0; trans_type < TRANS_TYPE_NUM; trans_type++) {
itmon_post_handler_to_notifier(itmon, trans_type);
itmon_post_handler_by_master(itmon, trans_type);
}
}
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;
unsigned int hwa_ctl, hwa_info, hwa_int_id, dbg_mo_cnt;
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);
dbg_mo_cnt = __raw_readl(node->regs + OFFSET_HW_ASSERT);
hwa_ctl = __raw_readl(node->regs + OFFSET_HW_ASSERT + REG_HWA_CTL);
hwa_info = __raw_readl(node->regs + OFFSET_HW_ASSERT + REG_HWA_INT);
hwa_int_id = __raw_readl(node->regs + OFFSET_HW_ASSERT + REG_HWA_INT_ID);
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_auto(ASL3, "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.dbg_mo_cnt = dbg_mo_cnt;
new_node->tracedata.hwa_ctl = hwa_ctl;
new_node->tracedata.hwa_info = hwa_info;
new_node->tracedata.hwa_int_id = hwa_int_id;
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_auto(ASL3, "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, freeze;
unsigned long vec, flags, bit = 0;
int i, j, ret = 0;
spin_lock_irqsave(&itmon->ctrl_lock, flags);
memset(pdata->traceinfo, 0, sizeof(struct itmon_traceinfo) * 2);
ret = 0;
if (group) {
/* Processing only this group and select detected node */
if (group->phy_regs) {
if (group->ex_table)
vec = (unsigned long)__raw_readq(group->regs);
else
vec = (unsigned long)__raw_readl(group->regs);
} else {
vec = GENMASK(group->nodesize, 0);
}
if (!vec)
goto exit;
node = group->nodeinfo;
for_each_set_bit(bit, &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;
}
}
/* Check H/W assertion */
if (node[bit].hw_assert_enabled) {
val = __raw_readl(node[bit].regs + OFFSET_HW_ASSERT +
REG_HWA_INT);
/* if timeout_freeze is enable,
* HWA interrupt is able to assert without any information */
if (BIT_HWA_ERR_OCCURRED(val) && (val & GENMASK(31, 1))) {
itmon_report_hwa_rawdata(itmon, &node[bit]);
pdata->err_hwa = true;
/* Go panic now */
pdata->err_cnt = PANIC_ALLOWED_THRESHOLD + 1;
ret = true;
}
}
/* Check freeze enable node */
if (node[bit].type == S_NODE && node[bit].tmout_frz_enabled) {
val = __raw_readl(node[bit].regs + OFFSET_TMOUT_REG +
REG_TMOUT_BUF_STATUS);
freeze = val & GENMASK(1, 0);
if (freeze) {
if (freeze & BIT(0))
itmon_report_timeout(itmon, &node[bit], TRANS_TYPE_WRITE);
if (freeze & BIT(1))
itmon_report_timeout(itmon, &node[bit], TRANS_TYPE_READ);
pdata->err_fatal = true;
/* Go panic now */
pdata->err_cnt = PANIC_ALLOWED_THRESHOLD + 1;
ret = true;
}
}
}
} else {
/* Processing all group & nodes */
for (i = 0; i < (int)ARRAY_SIZE(nodegroup); i++) {
group = &nodegroup[i];
if (group->phy_regs) {
if (group->ex_table)
vec = (unsigned long)__raw_readq(group->regs);
else
vec = (unsigned long)__raw_readl(group->regs);
} else {
vec = GENMASK(group->nodesize, 0);
}
node = group->nodeinfo;
bit = 0;
for_each_set_bit(bit, &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;
}
}
/* Check H/W assertion */
if (node[bit].hw_assert_enabled) {
val = __raw_readl(node[bit].regs + OFFSET_HW_ASSERT +
REG_HWA_INT);
/* if timeout_freeze is enable,
* HWA interrupt is able to assert without any information */
if (BIT_HWA_ERR_OCCURRED(val) && (val & GENMASK(31, 1))) {
itmon_report_hwa_rawdata(itmon, &node[bit]);
pdata->err_hwa = true;
/* Go panic now */
pdata->err_cnt = PANIC_ALLOWED_THRESHOLD + 1;
ret = true;
}
}
/* Check freeze enable node */
if (node[bit].type == S_NODE && node[bit].tmout_frz_enabled) {
val = __raw_readl(node[bit].regs + OFFSET_TMOUT_REG +
REG_TMOUT_BUF_STATUS);
freeze = val & (unsigned int)(GENMASK(1, 0));
if (freeze) {
if (freeze & BIT(0))
itmon_report_timeout(itmon, &node[bit], TRANS_TYPE_WRITE);
if (freeze & BIT(1))
itmon_report_timeout(itmon, &node[bit], TRANS_TYPE_READ);
pdata->err_fatal = true;
/* Go panic now */
pdata->err_cnt = PANIC_ALLOWED_THRESHOLD + 1;
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 < (int)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->sysfs_scandump) {
itmon_switch_scandump(itmon);
} else if (pdata->sysfs_s2d) {
itmon_switch_s2d(itmon);
}
if (pdata->err_cnt++ > PANIC_ALLOWED_THRESHOLD)
pdata->panic_allowed = true;
}
if (pdata->panic_allowed)
panic("ITMON occurs panic, Transaction is invalid from IPs");
return IRQ_HANDLED;
}
void itmon_notifier_chain_register(struct notifier_block *block)
{
atomic_notifier_chain_register(&itmon_notifier_list, block);
}
static struct bus_type itmon_subsys = {
.name = "itmon",
.dev_name = "itmon",
};
static ssize_t itmon_timeout_fix_val_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
ssize_t n = 0;
struct itmon_platdata *pdata = g_itmon->pdata;
n = scnprintf(buf + n, 24, "set timeout val: 0x%x \n", pdata->sysfs_tmout_val);
return n;
}
static ssize_t itmon_timeout_fix_val_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
unsigned int val = (unsigned int)simple_strtoul(buf, NULL, 0);
struct itmon_platdata *pdata = g_itmon->pdata;
if (val > 0 && val <= 0xFFFFF)
pdata->sysfs_tmout_val = val;
return count;
}
static ssize_t itmon_scandump_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
ssize_t n = 0;
struct itmon_platdata *pdata = g_itmon->pdata;
n = scnprintf(buf + n, 24, "scandump mode is %sable : %d\n",
pdata->sysfs_scandump == 1 ? "en" : "dis",
pdata->sysfs_scandump);
return n;
}
static ssize_t itmon_scandump_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
unsigned int val = (unsigned int)simple_strtoul(buf, NULL, 0);
struct itmon_platdata *pdata = g_itmon->pdata;
if (val > 0 && val <= 0xFFFFF) {
pdata = g_itmon->pdata;
pdata->sysfs_scandump = val;
}
return count;
}
#ifdef CONFIG_EXYNOS_ACPM_S2D
static ssize_t itmon_s2d_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
ssize_t n = 0;
struct itmon_platdata *pdata = g_itmon->pdata;
n = scnprintf(buf + n, 24, "s2d mode is %sable : %d\n",
pdata->sysfs_s2d == 1 ? "en" : "dis",
pdata->sysfs_s2d);
return n;
}
static ssize_t itmon_s2d_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
unsigned int val = simple_strtoul(buf, NULL, 0);
struct itmon_platdata *pdata = g_itmon->pdata;
if (val > 0 && val <= 0xFFFFF) {
pdata = g_itmon->pdata;
pdata->sysfs_s2d = val;
}
return count;
}
#endif
static ssize_t itmon_timeout_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
unsigned long i, offset;
ssize_t n = 0;
unsigned long vec, bit = 0;
struct itmon_nodegroup *group = NULL;
struct itmon_nodeinfo *node;
/* Processing all group & nodes */
offset = OFFSET_TMOUT_REG;
for (i = 0; i < ARRAY_SIZE(nodegroup); i++) {
group = &nodegroup[i];
node = group->nodeinfo;
vec = GENMASK(group->nodesize, 0);
bit = 0;
for_each_set_bit(bit, &vec, group->nodesize) {
if (node[bit].type == S_NODE) {
n += scnprintf(buf + n, 60, "%-12s : 0x%08X, timeout : %x\n",
node[bit].name, node[bit].phy_regs,
__raw_readl(node[bit].regs + offset + REG_DBG_CTL));
}
}
}
return n;
}
static ssize_t itmon_timeout_val_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
unsigned long i, offset;
ssize_t n = 0;
unsigned long vec, bit = 0;
struct itmon_nodegroup *group = NULL;
struct itmon_nodeinfo *node;
/* Processing all group & nodes */
offset = OFFSET_TMOUT_REG;
for (i = 0; i < ARRAY_SIZE(nodegroup); i++) {
group = &nodegroup[i];
node = group->nodeinfo;
vec = GENMASK(group->nodesize, 0);
bit = 0;
for_each_set_bit(bit, &vec, group->nodesize) {
if (node[bit].type == S_NODE) {
n += scnprintf(buf + n, 60, "%-12s : 0x%08X, timeout : 0x%x\n",
node[bit].name, node[bit].phy_regs,
__raw_readl(node[bit].regs + offset + REG_TMOUT_INIT_VAL));
}
}
}
return n;
}
static ssize_t itmon_timeout_freeze_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
unsigned long i, offset;
ssize_t n = 0;
unsigned long vec, bit = 0;
struct itmon_nodegroup *group = NULL;
struct itmon_nodeinfo *node;
/* Processing all group & nodes */
offset = OFFSET_TMOUT_REG;
for (i = 0; i < ARRAY_SIZE(nodegroup); i++) {
group = &nodegroup[i];
node = group->nodeinfo;
vec = GENMASK(group->nodesize, 0);
bit = 0;
for_each_set_bit(bit, &vec, group->nodesize) {
if (node[bit].type == S_NODE) {
n += scnprintf(buf + n, 60, "%-12s : 0x%08X, timeout_freeze : %x\n",
node[bit].name, node[bit].phy_regs,
__raw_readl(node[bit].regs + offset + REG_TMOUT_FRZ_EN));
}
}
}
return n;
}
static ssize_t itmon_timeout_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
char *name;
unsigned int val, offset, i;
unsigned long vec, bit = 0;
struct itmon_nodegroup *group = NULL;
struct itmon_nodeinfo *node;
name = (char *)kstrndup(buf, count, GFP_KERNEL);
if (!name)
return count;
name[count - 1] = '\0';
offset = OFFSET_TMOUT_REG;
for (i = 0; i < (int)ARRAY_SIZE(nodegroup); i++) {
group = &nodegroup[i];
node = group->nodeinfo;
vec = GENMASK(group->nodesize, 0);
bit = 0;
for_each_set_bit(bit, &vec, group->nodesize) {
if (node[bit].type == S_NODE &&
!strncmp(name, node[bit].name, strlen(name))) {
val = __raw_readl(node[bit].regs + offset + REG_DBG_CTL);
if (!val)
val = 1;
else
val = 0;
__raw_writel(val, node[bit].regs + offset + REG_DBG_CTL);
node[bit].tmout_enabled = val;
}
}
}
kfree(name);
return count;
}
static ssize_t itmon_timeout_val_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
char *name;
unsigned int offset, i;
unsigned long vec, bit = 0;
struct itmon_nodegroup *group = NULL;
struct itmon_nodeinfo *node;
struct itmon_platdata *pdata = g_itmon->pdata;
name = (char *)kstrndup(buf, count, GFP_KERNEL);
if (!name)
return count;
name[count - 1] = '\0';
offset = OFFSET_TMOUT_REG;
for (i = 0; i < (int)ARRAY_SIZE(nodegroup); i++) {
group = &nodegroup[i];
node = group->nodeinfo;
vec = GENMASK(group->nodesize, 0);
bit = 0;
for_each_set_bit(bit, &vec, group->nodesize) {
if (node[bit].type == S_NODE &&
!strncmp(name, node[bit].name, strlen(name))) {
__raw_writel(pdata->sysfs_tmout_val,
node[bit].regs + offset + REG_TMOUT_INIT_VAL);
node[bit].time_val = pdata->sysfs_tmout_val;
}
}
}
kfree(name);
return count;
}
static ssize_t itmon_timeout_freeze_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
char *name;
unsigned int val, offset, i;
unsigned long vec, bit = 0;
struct itmon_nodegroup *group = NULL;
struct itmon_nodeinfo *node;
name = (char *)kstrndup(buf, count, GFP_KERNEL);
if (!name)
return count;
name[count - 1] = '\0';
offset = OFFSET_TMOUT_REG;
for (i = 0; i < (int)ARRAY_SIZE(nodegroup); i++) {
group = &nodegroup[i];
node = group->nodeinfo;
vec = GENMASK(group->nodesize, 0);
bit = 0;
for_each_set_bit(bit, &vec, group->nodesize) {
if (node[bit].type == S_NODE &&
!strncmp(name, node[bit].name, strlen(name))) {
val = __raw_readl(node[bit].regs + offset + REG_TMOUT_FRZ_EN);
if (!val)
val = 1;
else
val = 0;
__raw_writel(val, node[bit].regs + offset + REG_TMOUT_FRZ_EN);
node[bit].tmout_frz_enabled = val;
}
}
}
kfree(name);
return count;
}
static struct kobj_attribute itmon_timeout_attr =
__ATTR(timeout_en, 0644, itmon_timeout_show, itmon_timeout_store);
static struct kobj_attribute itmon_timeout_fix_attr =
__ATTR(set_val, 0644, itmon_timeout_fix_val_show, itmon_timeout_fix_val_store);
static struct kobj_attribute itmon_scandump_attr =
__ATTR(scandump_en, 0644, itmon_scandump_show, itmon_scandump_store);
#ifdef CONFIG_EXYNOS_ACPM_S2D
static struct kobj_attribute itmon_s2d_attr =
__ATTR(s2d_en, 0644, itmon_s2d_show, itmon_s2d_store);
#endif
static struct kobj_attribute itmon_timeout_val_attr =
__ATTR(timeout_val, 0644, itmon_timeout_val_show, itmon_timeout_val_store);
static struct kobj_attribute itmon_timeout_freeze_attr =
__ATTR(timeout_freeze, 0644, itmon_timeout_freeze_show, itmon_timeout_freeze_store);
static struct attribute *itmon_sysfs_attrs[] = {
&itmon_timeout_attr.attr,
&itmon_timeout_fix_attr.attr,
&itmon_timeout_val_attr.attr,
&itmon_timeout_freeze_attr.attr,
&itmon_scandump_attr.attr,
#ifdef CONFIG_EXYNOS_ACPM_S2D
&itmon_s2d_attr.attr,
#endif
NULL,
};
static struct attribute_group itmon_sysfs_group = {
.attrs = itmon_sysfs_attrs,
};
static const struct attribute_group *itmon_sysfs_groups[] = {
&itmon_sysfs_group,
NULL,
};
static int __init itmon_sysfs_init(void)
{
int ret = 0;
ret = subsys_system_register(&itmon_subsys, itmon_sysfs_groups);
if (ret)
pr_err("fail to register exynos-snapshop subsys\n");
return ret;
}
late_initcall(itmon_sysfs_init);
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;
struct itmon_platdata *pdata = itmon->pdata;
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__);
if (pdata->sysfs_scandump) {
itmon_switch_scandump(itmon);
} else if (pdata->sysfs_s2d) {
itmon_switch_s2d(itmon);
}
}
}
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)
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)
return -ENOMEM;
itmon->pdata = pdata;
itmon->pdata->masterinfo = masterinfo;
itmon->pdata->rpathinfo = rpathinfo;
itmon->pdata->nodegroup = nodegroup;
for (i = 0; i < (int)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;
}
}
#ifdef MULTI_IRQ_SUPPORT_ITMON
irq_option = IRQF_GIC_MULTI_TARGET;
#endif
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_info(&pdev->dev, "success to register request irq%u - %s\n", irq, dev_name);
} else {
dev_err(&pdev->dev, "failed to request irq - %s\n", dev_name);
return -ENOENT;
}
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);
for (i = 0; i < TRANS_TYPE_NUM; i++)
INIT_LIST_HEAD(&pdata->tracelist[i]);
pdata->crash_in_progress = false;
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);
struct itmon_platdata *pdata = itmon->pdata;
/* re-enable ITMON if cp-crash progress is not starting */
if (!pdata->crash_in_progress)
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");