/* * Copyright (c) 2015 Samsung Electronics Co., Ltd. * http://www.samsung.com * * IPs Traffic Monitor(ITM) Driver for Samsung Exynos7570 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. */ #define pr_fmt(fmt) "[ITM] detect: " fmt #include #include #include #include #include #include #include #if defined(CONFIG_SEC_SIPC_MODEM_IF) #include #endif /* S-NODE, M-NODE Common */ #define OFFSET_TIMEOUT_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_TIMEOUT_INIT_VAL (0x14) #define REG_R_TIMEOUT_MO (0x18) #define REG_W_TIMEOUT_MO (0x1C) #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 S_NODE (0) #define M_NODE (1) #define T_S_NODE (2) #define T_M_NODE (3) #define DATA_BACKBONE_PATH (0) #define PERI_PATH (1) #define PATH_NUM (2) #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_TIMEOUT (6) #define TIMEOUT (0xFFFFF) #define TIMEOUT_TEST (0x1) #define NEED_TO_CHECK (0xCAFE) struct itm_rpathinfo { unsigned int id; char *port_name; char *dest_name; unsigned int bits; unsigned int shift_bits; }; struct itm_masterinfo { char *port_name; unsigned int user; char *master_name; unsigned int bits; }; struct itm_nodeinfo { unsigned int type; char *name; unsigned int phy_regs; void __iomem *regs; unsigned int time_val; bool timeout_enabled; bool err_rpt_enabled; bool retention; }; /* Error Code Description */ static char *itm_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", "Invalid errorcode", }; struct itm_nodegroup { int irq; char *name; unsigned int phy_regs; void __iomem *regs; struct itm_nodeinfo *nodeinfo; unsigned int nodesize; unsigned int irq_occurred; bool panic_delayed; }; struct itm_platdata { struct itm_rpathinfo *rpathinfo; struct itm_masterinfo *masterinfo; struct itm_nodegroup *nodegroup; bool probed; }; static struct itm_rpathinfo rpathinfo[] = { {0, "CPU", "DREX_CPU", GENMASK(0, 0), 1}, {1, "DBG", "DREX_CPU", GENMASK(0, 0), 1}, {0, "GNSS", "DREX_CP", GENMASK(1, 0), 2}, {1, "CP", "DREX_CP", GENMASK(1, 0), 2}, {2, "WFBT", "DREX_CP", GENMASK(1, 0), 2}, {0, "MFCMSCL", "DREX_MM", GENMASK(2, 0), 3}, {1, "G3D", "DREX_MM", GENMASK(2, 0), 3}, {2, "FSYS", "DREX_MM", GENMASK(2, 0), 3}, {3, "PDMA", "DREX_MM", GENMASK(2, 0), 3}, {4, "APM", "DREX_MM", GENMASK(2, 0), 3}, {5, "ISP", "DREX_MM", GENMASK(2, 0), 3}, {6, "DISPAUD", "DREX_MM", GENMASK(2, 0), 3}, {0, "CPU", "PERI", GENMASK(3, 0), 4}, {1, "DBG", "PERI", GENMASK(3, 0), 4}, {2, "MFCMSCL", "PERI", GENMASK(3, 0), 4}, {3, "G3D", "PERI", GENMASK(3, 0), 4}, {4, "FSYS", "PERI", GENMASK(3, 0), 4}, {5, "PDMA", "PERI", GENMASK(3, 0), 4}, {6, "APM", "PERI", GENMASK(3, 0), 4}, {7, "GNSS", "PERI", GENMASK(3, 0), 4}, {8, "CP", "PERI", GENMASK(3, 0), 4}, {9, "WFBT", "PERI", GENMASK(3, 0), 4}, {0, "CPU", "APMP", GENMASK(3, 0), 4}, {1, "DBG", "APMP", GENMASK(3, 0), 4}, {2, "MFCMSCL", "APMP", GENMASK(3, 0), 4}, {3, "G3D", "APMP", GENMASK(3, 0), 4}, {4, "FSYS", "APMP", GENMASK(3, 0), 4}, {5, "PDMA", "APMP", GENMASK(3, 0), 4}, {6, "APM", "APMP", GENMASK(3, 0), 4}, {7, "GNSS", "APMP", GENMASK(3, 0), 4}, {8, "CP", "APMP", GENMASK(3, 0), 4}, {9, "WFBT", "APMP", GENMASK(3, 0), 4}, }; /* XIU ID Information */ static struct itm_masterinfo masterinfo[] = { {"DISPAUD", BIT(3), "IDMA0", GENMASK(3, 2)}, {"DISPAUD", 0, "IDMA1", GENMASK(3, 2)}, {"DISPAUD", BIT(2), "IDMA2", GENMASK(3, 2)}, {"ISP", 0, "CSIS0", GENMASK(3, 1)}, {"ISP", BIT(1), "FIMC_ISP", GENMASK(3, 1)}, {"ISP", BIT(2), "MC_SCALER", GENMASK(3, 1)}, {"ISP", BIT(1) | BIT(2), "FIMC_VRA", GENMASK(3, 1)}, {"FSYS", 0, "MMC55-0", GENMASK(2, 0)}, {"FSYS", BIT(0), "MMC50-2", GENMASK(2, 0)}, {"FSYS", BIT(1), "SSS0", GENMASK(2, 0)}, {"FSYS", BIT(0) | BIT(1), "RTIC", GENMASK(2, 0)}, {"FSYS", BIT(2), "USB20DRD", GENMASK(2, 0)}, {"MFCMSCL", 0, "JPEG", GENMASK(2, 1)}, {"MFCMSCL", BIT(1), "M2M_Poly", GENMASK(2, 1)}, {"MFCMSCL", BIT(2), "M2M_Bl", GENMASK(2, 1)}, {"MFCMSCL", BIT(1) | BIT(2), "MFC", GENMASK(2, 1)}, {"CP", 0, "CR4M", GENMASK(4, 3)}, {"CP", BIT(3), "TL3MtoL2", GENMASK(4, 3)}, {"CP", BIT(4), "DMA", GENMASK(4, 2)}, {"CP", BIT(2) | BIT(4), "CSXAP", GENMASK(4, 0)}, {"CP", BIT(0) | BIT(2) | BIT(4), "DMtoL2", GENMASK(4, 0)}, {"CP", BIT(1) | BIT(2) | BIT(4), "LMAC", GENMASK(4, 0)}, {"CP", BIT(0) | BIT(1) | BIT(2) | BIT(4),"HMtoL2", GENMASK(4, 0)}, {"WFBT", 0, "SXCR4", GENMASK(1, 0)}, {"WFBT", BIT(0), "SXCR4", GENMASK(5, 3) | GENMASK(1, 0)}, {"WFBT", BIT(1), "SHBWL", GENMASK(3, 0)}, /* Others */ {"CPU", 0, "", 0}, {"DBG", 0, "", 0}, {"G3D", 0, "", 0}, {"PDMA", 0, "", 0}, {"APM", 0, "", 0}, }; /* data_backbone_path is sorted by INT_VEC_DEBUG_INTERRUPT_VECTOR_TABLE bits */ static struct itm_nodeinfo data_backbone_path[] = { {M_NODE, "APM", 0x12543000, NULL, TIMEOUT, true, true, false}, {M_NODE, "CP", 0x12483000, NULL, 0, true, true, false}, {M_NODE, "CPU", 0x12403000, NULL, 0, true, true, false}, {M_NODE, "DBG", 0x12413000, NULL, 0, false, true, false}, {M_NODE, "DISPAUD", 0x12463000, NULL, 0, false, true, false}, {M_NODE, "FSYS", 0x12443000, NULL, 0, false, true, false}, {M_NODE, "G3D", 0x12433000, NULL, 0, false, true, false}, {M_NODE, "GNSS", 0x12473000, NULL, 0, false, true, false}, {M_NODE, "ISP", 0x12453000, NULL, 0, false, true, false}, {M_NODE, "MFCMSCL", 0x12423000, NULL, 0, false, true, false}, {M_NODE, "PDMA", 0x12533000, NULL, 0, false, true, false}, {M_NODE, "WFBT", 0x12493000, NULL, 0, false, true, false}, {S_NODE, "APMP", 0x124E3000, NULL, TIMEOUT, true, true, false}, {S_NODE, "DREX_CP", 0x124B3000, NULL, TIMEOUT, true, true, false}, {S_NODE, "DREX_CPU", 0x124A3000, NULL, TIMEOUT, true, true, false}, {S_NODE, "DREX_MM", 0x124C3000, NULL, TIMEOUT, true, true, false}, {S_NODE, "PERI", 0x124D3000, NULL, TIMEOUT, true, true, false}, }; /* peri_path is sorted by INT_VEC_DEBUG_INTERRUPT_VECTOR_TABLE bits */ static struct itm_nodeinfo peri_path[] = { {M_NODE, "MIFND", 0x12603000, NULL, 0, false, true, false}, {S_NODE, "BUS_SFR", 0x12693000, NULL, TIMEOUT, true, true, false}, {S_NODE, "CPU_SFR", 0x12623000, NULL, TIMEOUT, true, true, false}, {S_NODE, "DISPAUD_SFR", 0x12673000, NULL, TIMEOUT, true, true, false}, {S_NODE, "FSYS_SFR", 0x12653000, NULL, TIMEOUT, true, true, false}, {S_NODE, "G3D_SFR", 0x12643000, NULL, TIMEOUT, true, true, false}, {S_NODE, "GIC_SFR", 0x126B3000, NULL, TIMEOUT, true, true, false}, {S_NODE, "ISP_SFR", 0x12663000, NULL, TIMEOUT, true, true, false}, {S_NODE, "MFCMSCL_SFR", 0x12663000, NULL, TIMEOUT, true, true, false}, {S_NODE, "MIF_SFR", 0x126A3000, NULL, TIMEOUT, true, true, false}, {S_NODE, "PERI_SFR", 0x12683000, NULL, TIMEOUT, true, true, false}, }; static struct itm_nodegroup nodegroup[] = { {378, "DATA_BACKBONE",0x125F3000, NULL, data_backbone_path, ARRAY_SIZE(data_backbone_path), 0, false}, {382, "PERI", 0x126F3000, NULL, peri_path, ARRAY_SIZE(peri_path), 0, false}, }; struct itm_dev { struct device *dev; struct itm_platdata *pdata; struct of_device_id *match; int irq; int id; void __iomem *regs; spinlock_t ctrl_lock; struct itm_notifier notifier_info; }; struct itm_panic_block { struct notifier_block nb_panic_block; struct itm_dev *pdev; }; /* declare notifier_list */ static ATOMIC_NOTIFIER_HEAD(itm_notifier_list); static const struct of_device_id itm_dt_match[] = { { .compatible = "samsung,exynos-itm", .data = NULL, }, {}, }; MODULE_DEVICE_TABLE(of, itm_dt_match); static struct itm_rpathinfo* itm_get_rpathinfo (struct itm_dev *itm, unsigned int id, char *dest_name) { struct itm_platdata *pdata = itm->pdata; struct itm_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 = &pdata->rpathinfo[i]; break; } } } return rpath; } static struct itm_masterinfo* itm_get_masterinfo (struct itm_dev *itm, char *port_name, unsigned int user) { struct itm_platdata *pdata = itm->pdata; struct itm_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 = &pdata->masterinfo[i]; break; } } } return master; } static void itm_init(struct itm_dev *itm, bool enabled) { struct itm_platdata *pdata = itm->pdata; struct itm_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].timeout_enabled) { offset = OFFSET_TIMEOUT_REG; /* Enable Timeout setting */ __raw_writel(enabled, node[j].regs + offset + REG_DBG_CTL); /* set timeout interval value */ __raw_writel(node[j].time_val, node[j].regs + offset + REG_TIMEOUT_INIT_VAL); pr_debug("Exynos IPM - %s timeout enabled\n", node[j].name); } if (node[j].err_rpt_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); } } } } static void itm_post_handler_by_master(struct itm_dev *itm, struct itm_nodegroup *group, char *port, char *master, bool read) { /* After treatment by port */ if (!port || strlen(port) < 1) return; if (!strncmp(port, "CP", strlen(port))) { /* if master is DSP and operation is read, we don't care this */ if (master && !strncmp(master, "TL3MtoL2",strlen(master)) && read == true) { group->panic_delayed = true; group->irq_occurred = 0; pr_info("ITM skips CP's DSP(TL3MtoL2) detected\n"); } else { /* Disable busmon all interrupts */ itm_init(itm, false); group->panic_delayed = true; #if defined(CONFIG_SEC_SIPC_MODEM_IF) ss310ap_force_crash_exit_ext(); #endif } } else if (!strncmp(port, "CPU", strlen(port))) { pr_info("ITM is disabled for CPU exception\n"); /* Disable busmon all interrupts */ itm_init(itm, false); group->panic_delayed = true; } } static void itm_report_route(struct itm_dev *itm, struct itm_nodegroup *group, struct itm_nodeinfo *node, unsigned int offset, bool read) { struct itm_masterinfo *master = NULL; struct itm_rpathinfo *rpath = NULL; unsigned int val, id; char *port = NULL, *source = NULL, *dest = NULL; val = __raw_readl(node->regs + offset + REG_INT_INFO); id = BIT_AXID(val); if (!strncmp(group->name, "DATA_BACKBONE", strlen(group->name))) { /* Data Path */ if (node->type == S_NODE) { rpath = itm_get_rpathinfo(itm, id, node->name); if (!rpath) { pr_info("failed to get route path - %s, id:%x\n", node->name, id); return; } id = (id >> rpath->shift_bits); master = itm_get_masterinfo(itm, rpath->port_name, id); if (!master) { pr_info("failed to get master IP with " "port:%s, id:%x\n", rpath->port_name, id); return; } port = rpath->port_name; source = master->master_name; dest = rpath->dest_name; } else { master = itm_get_masterinfo(itm, node->name, id); if (!master) { pr_info("failed to get master IP with " "port:%s, id:%x\n", node->name, id); return; } port = node->name; source = master->master_name; } } else { /* * PERI_PATH * In this case, we will get the route table from DATA_BACKBONE interrupt * of PERI port */ if (node->type == S_NODE) { dest = node->name; } else { port = node->name; } } pr_info("\n--------------------------------------------------------------------------------\n\n" "ROUTE INFORMATION - %s\n" "> Master : %s %s\n" "> Slave : %s\n\n", group->name, port ? port : "Note other NODE Information", source ? source : "", dest ? dest : "Note other NODE Information"); itm_post_handler_by_master(itm, group, port, source, read); } static void itm_report_info(struct itm_dev *itm, struct itm_nodegroup *group, struct itm_nodeinfo *node, unsigned int offset) { unsigned int errcode, int_info, info0, info1, info2; bool read = false, req = false; int_info = __raw_readl(node->regs + offset + REG_INT_INFO); if (!BIT_ERR_VALID(int_info)) { pr_info("no information, %s/offset:%x is stopover, " "check other node\n", node->name, offset); return; } errcode = BIT_ERR_CODE(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 = true; /* fall down */ case OFFSET_REQ_W: req = true; if (node->type == S_NODE) { /* Only S-Node is able to make log to registers */ pr_info("invalid logged, see more following information\n"); goto out; } break; case OFFSET_RESP_R: read = true; /* fall down */ case OFFSET_RESP_W: req = false; if (node->type != S_NODE) { /* Only S-Node is able to make log to registers */ pr_info("invalid logged, see more following information\n"); goto out; } break; default: pr_info("Unknown Error - offset:%u\n", offset); goto out; } /* Normally fall down to here */ itm_report_route(itm, group, node, offset, read); pr_info("\n--------------------------------------------------------------------------------\n\n" "TRANSACTION INFORMATION\n" "> Path Type : %s in %s %s \n" "> Target address : 0x%08X\n" "> Error type : %s\n\n", group->name, read ? "READ" : "WRITE", req ? "REQUEST" : "RESPONSE", info0, itm_errcode[errcode]); out: /* report extention raw information of register */ pr_info("\n--------------------------------------------------------------------------------\n\n" "NODE RAW INFORMATION\n" "> NODE NAME : %s(%s)\n" "> NODE ADDRESS : 0x%08X\n" "> INTERRUPT_INFO : 0x%08X\n" "> EXT_INFO_0 : 0x%08X\n" "> EXT_INFO_1 : 0x%08X\n" "> EXT_INFO_2 : 0x%08X\n\n" "--------------------------------------------------------------------------------\n", node->name, node->type ? "M_NODE" : "S_NODE", node->phy_regs + offset, int_info, info0, info1, info2); } static int itm_parse_info(struct itm_dev *itm, struct itm_nodegroup *group, bool clear) { //struct itm_platdata *pdata = itm->pdata; struct itm_nodeinfo *node = NULL; unsigned int val, offset, vec; unsigned long flags, bit = 0; int i, j, ret = 0; spin_lock_irqsave(&itm->ctrl_lock, flags); if (group) { /* Processing only this group and select detected node */ vec = __raw_readl(group->regs); node = group->nodeinfo; if (!vec) pr_info("invalid detection\n"); 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 */ itm_report_info(itm, 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]; vec = __raw_readl(group->regs); 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 */ itm_report_info(itm, group, &node[bit], offset); if (clear) __raw_writel(1, node[j].regs + offset + REG_INT_CLR); ret = true; } } } } } spin_unlock_irqrestore(&itm->ctrl_lock, flags); return ret; } static irqreturn_t itm_irq_handler(int irq, void *data) { struct itm_dev *itm = (struct itm_dev *)data; struct itm_platdata *pdata = itm->pdata; struct itm_nodegroup *group = NULL; bool ret; int i; /* convert from raw irq source to SPI irq number */ irq = irq - 32; /* Search itm group */ for (i = 0; i < ARRAY_SIZE(nodegroup); i++) { if (irq == nodegroup[i].irq) { pr_info("%s group, %d interrupt occurrs \n", pdata->nodegroup[i].name, irq); group = &pdata->nodegroup[i]; break; } } if (group) { ret = itm_parse_info(itm, group, true); if (!ret) { pr_info("can't process %s irq:%d IRQ_VECTOR:%x\n", group->name, irq, __raw_readl(group->regs)); } else { if (group->irq_occurred && !group->panic_delayed) panic("STOP infinite output by Exynos ITM"); else group->irq_occurred++; } } else { pr_info("can't the group - irq:%d\n", irq); } return IRQ_HANDLED; } void itm_notifier_chain_register(struct notifier_block *block) { atomic_notifier_chain_register(&itm_notifier_list, block); } static int itm_logging_panic_handler(struct notifier_block *nb, unsigned long l, void *buf) { struct itm_panic_block *itm_panic = (struct itm_panic_block *)nb; struct itm_dev *itm = itm_panic->pdev; int ret; if (!IS_ERR_OR_NULL(itm)) { /* Check error has been logged */ ret = itm_parse_info(itm, 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 itm_probe(struct platform_device *pdev) { struct itm_dev *itm; struct itm_panic_block *itm_panic = NULL; struct itm_platdata *pdata; struct itm_nodeinfo *node; char *dev_name; int ret, i, j; itm = devm_kzalloc(&pdev->dev, sizeof(struct itm_dev), GFP_KERNEL); if (!itm) { dev_err(&pdev->dev, "failed to allocate memory for driver's " "private data\n"); return -ENOMEM; } itm->dev = &pdev->dev; spin_lock_init(&itm->ctrl_lock); pdata = devm_kzalloc(&pdev->dev, sizeof(struct itm_platdata), GFP_KERNEL); if (!pdata) { dev_err(&pdev->dev, "failed to allocate memory for driver's " "platform data\n"); return -ENOMEM; } itm->pdata = pdata; itm->pdata->masterinfo = masterinfo; itm->pdata->rpathinfo = rpathinfo; itm->pdata->nodegroup = nodegroup; for (i = 0; i < ARRAY_SIZE(nodegroup); i++) { dev_name = nodegroup[i].name; node = nodegroup[i].nodeinfo; 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; } ret = devm_request_irq(&pdev->dev, nodegroup[i].irq + 32, itm_irq_handler, 0, //IRQF_GIC_MULTI_TARGET, dev_name, itm); 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; } } } itm_panic = devm_kzalloc(&pdev->dev, sizeof(struct itm_panic_block), GFP_KERNEL); if (!itm_panic) { dev_err(&pdev->dev, "failed to allocate memory for driver's " "panic handler data\n"); } else { itm_panic->nb_panic_block.notifier_call = itm_logging_panic_handler; itm_panic->pdev = itm; atomic_notifier_chain_register(&panic_notifier_list, &itm_panic->nb_panic_block); } platform_set_drvdata(pdev, itm); itm_init(itm, true); pdata->probed = true; dev_info(&pdev->dev, "success to probe ITM driver\n"); return 0; } static int itm_remove(struct platform_device *pdev) { platform_set_drvdata(pdev, NULL); return 0; } #ifdef CONFIG_PM_SLEEP static int itm_suspend(struct device *dev) { return 0; } static int itm_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct itm_dev *itm = platform_get_drvdata(pdev); itm_init(itm, true); return 0; } static SIMPLE_DEV_PM_OPS(itm_pm_ops, itm_suspend, itm_resume); #define ITM_PM (itm_pm_ops) #else #define ITM_PM NULL #endif static struct platform_driver exynos_itm_driver = { .probe = itm_probe, .remove = itm_remove, .driver = { .name = "exynos-itm", .of_match_table = itm_dt_match, .pm = &itm_pm_ops, }, }; module_platform_driver(exynos_itm_driver); MODULE_DESCRIPTION("Samsung Exynos ITM DRIVER"); MODULE_AUTHOR("Hosung Kim