diff options
author | Runmin Wang <runminw@codeaurora.org> | 2016-03-29 16:32:39 -0700 |
---|---|---|
committer | Jeevan Shriram <jshriram@codeaurora.org> | 2016-04-18 17:26:48 -0700 |
commit | f15492d20c2c93636cee175bde321ef229ad45be (patch) | |
tree | 8e2d76507ea78bd52ccaca574b54c166b9f0f4e8 | |
parent | 5dca066217e07c7828d298d3254e5f6919f5dee0 (diff) |
soc: qcom: Add support for gladiator error reporting v2
Add support for gladiator cache inter connect error detection
and reporting for msmcobalt
CRs-Fixed: 1000642
Change-Id: I68c5ce09cc77a19eb334a1d8ccce8d577f964316
Signed-off-by: Runmin Wang <runminw@codeaurora.org>
-rw-r--r-- | Documentation/devicetree/bindings/cache/msm_gladiator_erp_v2.txt | 20 | ||||
-rw-r--r-- | drivers/soc/qcom/Kconfig | 19 | ||||
-rw-r--r-- | drivers/soc/qcom/Makefile | 1 | ||||
-rw-r--r-- | drivers/soc/qcom/gladiator_erp_v2.c | 799 |
4 files changed, 839 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/cache/msm_gladiator_erp_v2.txt b/Documentation/devicetree/bindings/cache/msm_gladiator_erp_v2.txt new file mode 100644 index 000000000000..3c1c5c010ba1 --- /dev/null +++ b/Documentation/devicetree/bindings/cache/msm_gladiator_erp_v2.txt @@ -0,0 +1,20 @@ +* MSM Gladiator error reporting driver + +Required properties: +- compatible: Should be "qcom,msm-gladiator-v2" +- reg: I/O address Gladiator H/W block +- reg-names: Should be "gladiator_base" +- interrupts: Should contain the gladiator error interrupt number +- clock-names: Should be "atb_clk" +- clocks: Handles to clocks specified in "clock-names" property. + +Example: + +qcom,msm-gladiator-v2@b1c0000 { + compatible = "qcom,msm-gladiator-v2"; + reg = <0xb1c0000 0xe000>; + reg-names = "gladiator_base"; + interrupts = <0 34 0>; + clock-names = "atb_clk"; + clocks = <&clock_gcc clk_qdss_clk>; +} diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index b6e2a55d5a9e..ba135e354ace 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -427,6 +427,25 @@ config MSM_GLADIATOR_ERP If unsure, say N. +config MSM_GLADIATOR_ERP_V2 + tristate "GLADIATOR coherency interconnect error reporting driver v2" + help + Support dumping debug information for the GLADIATOR + cache interconnect in the error interrupt handler. + Meant to be used for debug scenarios only. + + If unsure, say N. + +config PANIC_ON_GLADIATOR_ERROR_V2 + depends on MSM_GLADIATOR_ERP_V2 + bool "Panic on GLADIATOR error report v2" + help + Panic upon detection of an Gladiator coherency interconnect error + in order to support dumping debug information. + Meant to be used for debug scenarios only. + + If unsure, say N. + config MSM_GLADIATOR_HANG_DETECT tristate "MSM Gladiator Hang Detection Support" help diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 1a4757f16e77..2433e81a831b 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -74,6 +74,7 @@ obj-$(CONFIG_MSM_MPM_OF) += mpm-of.o obj-$(CONFIG_MSM_EVENT_TIMER) += event_timer.o obj-$(CONFIG_MSM_TZ_SMMU) += msm_tz_smmu.o obj-$(CONFIG_MSM_GLADIATOR_ERP) += gladiator_erp.o +obj-$(CONFIG_MSM_GLADIATOR_ERP_V2) += gladiator_erp_v2.o obj-$(CONFIG_MSM_CORE_HANG_DETECT) += core_hang_detect.o obj-$(CONFIG_MSM_GLADIATOR_HANG_DETECT) += gladiator_hang_detect.o obj-$(CONFIG_MSM_RUN_QUEUE_STATS) += msm_rq_stats.o diff --git a/drivers/soc/qcom/gladiator_erp_v2.c b/drivers/soc/qcom/gladiator_erp_v2.c new file mode 100644 index 000000000000..70aace270acf --- /dev/null +++ b/drivers/soc/qcom/gladiator_erp_v2.c @@ -0,0 +1,799 @@ +/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/cpu_pm.h> +#include <linux/platform_device.h> +#include <soc/qcom/scm.h> +#include <linux/of.h> +#include <linux/clk.h> + +#define MODULE_NAME "gladiator-v2_error_reporting" + +/* Register Offsets */ +#define GLADIATOR_ID_COREID 0x0 +#define GLADIATOR_ID_REVISIONID 0x4 +#define GLADIATOR_FAULTEN 0x1010 +#define GLADIATOR_ERRVLD 0x1014 +#define GLADIATOR_ERRCLR 0x1018 +#define GLADIATOR_ERRLOG0 0x101C +#define GLADIATOR_ERRLOG1 0x1020 +#define GLADIATOR_ERRLOG2 0x1024 +#define GLADIATOR_ERRLOG3 0x1028 +#define GLADIATOR_ERRLOG4 0x102C +#define GLADIATOR_ERRLOG5 0x1030 +#define GLADIATOR_ERRLOG6 0x1034 +#define GLADIATOR_ERRLOG7 0x1038 +#define GLADIATOR_ERRLOG8 0x103C +#define OBSERVER_0_ID_COREID 0x8000 +#define OBSERVER_0_FAULTEN 0x8008 +#define OBSERVER_0_ERRVLD 0x800C +#define OBSERVER_0_ERRCLR 0x8010 +#define OBSERVER_0_ERRLOG0 0x8014 +#define OBSERVER_0_ERRLOG1 0x8018 +#define OBSERVER_0_ERRLOG2 0x801C +#define OBSERVER_0_ERRLOG3 0x8020 +#define OBSERVER_0_ERRLOG4 0x8024 +#define OBSERVER_0_ERRLOG5 0x8028 +#define OBSERVER_0_ERRLOG6 0x802C +#define OBSERVER_0_ERRLOG7 0x8030 +#define OBSERVER_0_ERRLOG8 0x8034 +#define OBSERVER_0_STALLEN 0x8038 +#define OBSERVER_0_REVISIONID 0x8004 + +#define GLD_TRANS_OPCODE_MASK 0xE +#define GLD_TRANS_OPCODE_SHIFT 1 +#define GLD_ERROR_TYPE_MASK 0x700 +#define GLD_ERROR_TYPE_SHIFT 8 +#define GLD_LEN1_MASK 0xFFF0000 +#define GLD_LEN1_SHIFT 16 +#define GLD_TRANS_SOURCEID_MASK 0x7 +#define GLD_TRANS_SOURCEID_SHIFT 0 +#define GLD_TRANS_TARGETID_MASK 0x7 +#define GLD_TRANS_TARGETID_SHIFT 0 +#define GLD_ERRLOG_ERROR 0x7 +#define GLD_ERRLOG5_ERROR_TYPE_MASK 0xFF000000 +#define GLD_ERRLOG5_ERROR_TYPE_SHIFT 24 +#define GLD_ACE_PORT_PARITY_MASK 0xc000 +#define GLD_ACE_PORT_PARITY_SHIFT 14 +#define GLD_ACE_PORT_DISCONNECT_MASK 0xf0000 +#define GLD_ACE_PORT_DISCONNECT_SHIFT 16 +#define GLD_ACE_PORT_DIRECTORY_MASK 0xf00000 +#define GLD_ACE_PORT_DIRECTORY_SHIFT 20 +#define GLD_INDEX_PARITY_MASK 0x1FFF +#define GLD_INDEX_PARITY_SHIFT 0 +#define OBS_TRANS_OPCODE_MASK 0x1E +#define OBS_TRANS_OPCODE_SHIFT 1 +#define OBS_ERROR_TYPE_MASK 0x700 +#define OBS_ERROR_TYPE_SHIFT 8 +#define OBS_LEN1_MASK 0x7F0000 +#define OBS_LEN1_SHIFT 16 + +struct msm_gladiator_data { + void __iomem *gladiator_virt_base; + int erp_irq; + struct notifier_block pm_notifier_block; + struct clk *qdss_clk; +}; + +static int enable_panic_on_error; +module_param(enable_panic_on_error, int, 0); + +enum gld_trans_opcode { + GLD_RD, + GLD_RDX, + GLD_RDL, + GLD_RESERVED, + GLD_WR, + GLD_WRC, + GLD_PRE, +}; + +enum obs_trans_opcode { + OBS_RD, + OBS_RDW, + OBS_RDL, + OBS_RDX, + OBS_WR, + OBS_WRW, + OBS_WRC, + OBS_RESERVED, + OBS_PRE, + OBS_URG, +}; + +enum obs_err_code { + OBS_SLV, + OBS_DEC, + OBS_UNS, + OBS_DISC, + OBS_SEC, + OBS_HIDE, + OBS_TMO, + OBS_RSV, +}; + +enum err_log { + ERR_LOG0, + ERR_LOG1, + ERR_LOG2, + ERR_LOG3, + ERR_LOG4, + ERR_LOG5, + ERR_LOG6, + ERR_LOG7, + ERR_LOG8, + STALLEN, +}; + +enum type_logger_error { + DATA_TRANSFER_ERROR, + DVM_ERROR, + TX_ERROR, + TXR_ERROR, + DISCONNECT_ERROR, + DIRECTORY_ERROR, + PARITY_ERROR, +}; + +static void clear_gladiator_error(void __iomem *gladiator_virt_base) +{ + writel_relaxed(1, gladiator_virt_base + GLADIATOR_ERRCLR); + writel_relaxed(1, gladiator_virt_base + OBSERVER_0_ERRCLR); +} + +static inline void print_gld_transaction(unsigned int opc) +{ + switch (opc) { + case GLD_RD: + pr_alert("Transaction type: READ\n"); + break; + case GLD_RDX: + pr_alert("Transaction type: EXCLUSIVE READ\n"); + break; + case GLD_RDL: + pr_alert("Transaction type: LINKED READ\n"); + break; + case GLD_WR: + pr_alert("Transaction type: WRITE\n"); + break; + case GLD_WRC: + pr_alert("Transaction type: CONDITIONAL WRITE\n"); + break; + case GLD_PRE: + pr_alert("Transaction: Preamble packet of linked sequence\n"); + break; + default: + pr_alert("Transaction type: Unknown; value:%u\n", opc); + } +} + +static inline void print_gld_errtype(unsigned int errtype) +{ + if (errtype == 0) + pr_alert("Error type: Snoop data transfer\n"); + else if (errtype == 1) + pr_alert("Error type: DVM error\n"); + else if (errtype == 3) + pr_alert("Error type: Disconnect, directory, or parity error\n"); + else + pr_alert("Error type: Unknown; value:%u\n", errtype); +} + +static void decode_gld_errlog0(u32 err_reg) +{ + unsigned int opc, errtype, len1; + + opc = (err_reg & GLD_TRANS_OPCODE_MASK) >> GLD_TRANS_OPCODE_SHIFT; + errtype = (err_reg & GLD_ERROR_TYPE_MASK) >> GLD_ERROR_TYPE_SHIFT; + len1 = (err_reg & GLD_LEN1_MASK) >> GLD_LEN1_SHIFT; + + print_gld_transaction(opc); + print_gld_errtype(errtype); + pr_alert("number of payload bytes: %d\n", len1 + 1); +} + +static void decode_gld_errlog1(u32 err_reg) +{ + if ((err_reg & GLD_ERRLOG_ERROR) == GLD_ERRLOG_ERROR) + pr_alert("Transaction issued on IO target generic interface\n"); + else + pr_alert("Transaction source ID: %d\n", + (err_reg & GLD_TRANS_SOURCEID_MASK) + >> GLD_TRANS_SOURCEID_SHIFT); +} + +static void decode_gld_errlog2(u32 err_reg) +{ + if ((err_reg & GLD_ERRLOG_ERROR) == GLD_ERRLOG_ERROR) + pr_alert("Error response coming from: external DVM network\n"); + else + pr_alert("Error response coming from: Target ID: %d\n", + (err_reg & GLD_TRANS_TARGETID_MASK) + >> GLD_TRANS_TARGETID_SHIFT); +} + +static void decode_ace_port_index(u32 type, u32 error) +{ + unsigned port; + + switch (type) { + case DISCONNECT_ERROR: + port = (error & GLD_ACE_PORT_DISCONNECT_MASK) + >> GLD_ACE_PORT_DISCONNECT_SHIFT; + pr_alert("ACE port index: %d\n", port); + break; + case DIRECTORY_ERROR: + port = (error & GLD_ACE_PORT_DIRECTORY_MASK) + >> GLD_ACE_PORT_DIRECTORY_SHIFT; + pr_alert("ACE port index: %d\n", port); + break; + case PARITY_ERROR: + port = (error & GLD_ACE_PORT_PARITY_MASK) + >> GLD_ACE_PORT_PARITY_SHIFT; + pr_alert("ACE port index: %d\n", port); + } +} + +static void decode_index_parity(u32 error) +{ + pr_alert("Index: %d\n", + (error & GLD_INDEX_PARITY_MASK) + >> GLD_INDEX_PARITY_SHIFT); +} + +static void decode_gld_logged_error(u32 err_reg5) +{ + unsigned int log_err_type, i, value; + + log_err_type = (err_reg5 & GLD_ERRLOG5_ERROR_TYPE_MASK) + >> GLD_ERRLOG5_ERROR_TYPE_SHIFT; + for (i = 0 ; i <= 6 ; i++) { + value = log_err_type & 0x1; + switch (i) { + case DATA_TRANSFER_ERROR: + if (value == 0) + continue; + pr_alert("Error type: Data transfer error\n"); + break; + case DVM_ERROR: + if (value == 0) + continue; + pr_alert("Error type: DVM error\n"); + break; + case TX_ERROR: + if (value == 0) + continue; + pr_alert("Error type: Tx error\n"); + break; + case TXR_ERROR: + if (value == 0) + continue; + pr_alert("Error type: TxR error\n"); + break; + case DISCONNECT_ERROR: + if (value == 0) + continue; + pr_alert("Error type: Disconnect error\n"); + decode_ace_port_index( + DISCONNECT_ERROR, + err_reg5); + break; + case DIRECTORY_ERROR: + if (value == 0) + continue; + pr_alert("Error type: Directory error\n"); + decode_ace_port_index( + DIRECTORY_ERROR, + err_reg5); + break; + case PARITY_ERROR: + if (value == 0) + continue; + pr_alert("Error type: Parity error\n"); + decode_ace_port_index(PARITY_ERROR, err_reg5); + decode_index_parity(err_reg5); + break; + } + log_err_type = log_err_type >> 1; + } +} + +static void decode_gld_errlog(u32 err_reg, unsigned int err_log) +{ + switch (err_log) { + case ERR_LOG0: + decode_gld_errlog0(err_reg); + break; + case ERR_LOG1: + decode_gld_errlog1(err_reg); + break; + case ERR_LOG2: + decode_gld_errlog2(err_reg); + break; + case ERR_LOG3: + pr_alert("Lower 32-bits of error address: %08x\n", err_reg); + break; + case ERR_LOG4: + pr_alert("Upper 32-bits of error address: %08x\n", err_reg); + break; + case ERR_LOG5: + pr_alert("Lower 32-bits of user: %08x\n", err_reg); + break; + case ERR_LOG6: + pr_alert("Mid 32-bits(63-32) of user: %08x\n", err_reg); + break; + case ERR_LOG7: + break; + case ERR_LOG8: + pr_alert("Upper 32-bits(95-64) of user: %08x\n", err_reg); + break; + default: + pr_alert("Invalid error register; reg num:%u\n", err_log); + } +} + +static inline void print_obs_transaction(unsigned int opc) +{ + switch (opc) { + case OBS_RD: + pr_alert("Transaction type: READ\n"); + break; + case OBS_RDW: + pr_alert("Transaction type: WRAPPED READ\n"); + break; + case OBS_RDL: + pr_alert("Transaction type: LINKED READ\n"); + break; + case OBS_RDX: + pr_alert("Transaction type: EXCLUSIVE READ\n"); + break; + case OBS_WR: + pr_alert("Transaction type: WRITE\n"); + break; + case OBS_WRW: + pr_alert("Transaction type: WRAPPED WRITE\n"); + break; + case OBS_WRC: + pr_alert("Transaction type: CONDITIONAL WRITE\n"); + break; + case OBS_PRE: + pr_alert("Transaction: Preamble packet of linked sequence\n"); + break; + case OBS_URG: + pr_alert("Transaction type: Urgency Packet\n"); + break; + default: + pr_alert("Transaction type: Unknown; value:%u\n", opc); + } +} + +static inline void print_obs_errcode(unsigned int errcode) +{ + switch (errcode) { + case OBS_SLV: + pr_alert("Error code: Target error detected by slave\n"); + pr_alert("Source: Target\n"); + break; + case OBS_DEC: + pr_alert("Error code: Address decode error\n"); + pr_alert("Source: Initiator NIU\n"); + break; + case OBS_UNS: + pr_alert("Error code: Unsupported request\n"); + pr_alert("Source: Target NIU\n"); + break; + case OBS_DISC: + pr_alert("Error code: Disconnected target or domain\n"); + pr_alert("Source: Power Disconnect\n"); + break; + case OBS_SEC: + pr_alert("Error code: Security violation\n"); + pr_alert("Source: Initiator NIU or Firewall\n"); + break; + case OBS_HIDE: + pr_alert("Error :Hidden security violation, reported as OK\n"); + pr_alert("Source: Firewall\n"); + break; + case OBS_TMO: + pr_alert("Error code: Time-out\n"); + pr_alert("Source: Target NIU\n"); + break; + default: + pr_alert("Error code: Unknown; code:%u\n", errcode); + } +} + +static void decode_obs_errlog0(u32 err_reg) +{ + unsigned int opc, errcode, len1; + + opc = (err_reg & OBS_TRANS_OPCODE_MASK) >> OBS_TRANS_OPCODE_SHIFT; + errcode = (err_reg & OBS_ERROR_TYPE_MASK) >> OBS_ERROR_TYPE_SHIFT; + len1 = (err_reg & OBS_LEN1_MASK) >> OBS_LEN1_SHIFT; + + print_obs_transaction(opc); + print_obs_errcode(errcode); + pr_alert("number of payload bytes: %d\n", len1 + 1); +} + +static void decode_obs_errlog(u32 err_reg, unsigned int err_log) +{ + switch (err_log) { + case ERR_LOG0: + decode_obs_errlog0(err_reg); + break; + case ERR_LOG1: + pr_alert("RouteId of the error: %08x\n", err_reg); + break; + case ERR_LOG2: + /* reserved error log register */ + break; + case ERR_LOG3: + pr_alert("Lower 32-bits of error address: %08x\n", err_reg); + break; + case ERR_LOG4: + pr_alert("Upper 12-bits of error address: %08x\n", err_reg); + break; + case ERR_LOG5: + pr_alert("Lower 13-bits of user: %08x\n", err_reg); + break; + case ERR_LOG6: + /* reserved error log register */ + break; + case ERR_LOG7: + pr_alert("Security filed of the logged error: %08x\n", err_reg); + break; + case ERR_LOG8: + /* reserved error log register */ + break; + case STALLEN: + pr_alert("stall mode of the error logger: %08x\n", + err_reg & 0x1); + break; + default: + pr_alert("Invalid error register; reg num:%u\n", err_log); + } +} + +static u32 get_gld_offset(unsigned int err_log) +{ + u32 offset = 0; + + switch (err_log) { + case ERR_LOG0: + offset = GLADIATOR_ERRLOG0; + break; + case ERR_LOG1: + offset = GLADIATOR_ERRLOG1; + break; + case ERR_LOG2: + offset = GLADIATOR_ERRLOG2; + break; + case ERR_LOG3: + offset = GLADIATOR_ERRLOG3; + break; + case ERR_LOG4: + offset = GLADIATOR_ERRLOG4; + break; + case ERR_LOG5: + offset = GLADIATOR_ERRLOG5; + break; + case ERR_LOG6: + offset = GLADIATOR_ERRLOG6; + break; + case ERR_LOG7: + offset = GLADIATOR_ERRLOG7; + break; + case ERR_LOG8: + offset = GLADIATOR_ERRLOG8; + break; + default: + pr_alert("Invalid gladiator error register; reg num:%u\n", + err_log); + } + return offset; +} + +static u32 get_obs_offset(unsigned int err_log) +{ + u32 offset = 0; + + switch (err_log) { + case ERR_LOG0: + offset = OBSERVER_0_ERRLOG0; + break; + case ERR_LOG1: + offset = OBSERVER_0_ERRLOG1; + break; + case ERR_LOG2: + offset = OBSERVER_0_ERRLOG2; + break; + case ERR_LOG3: + offset = OBSERVER_0_ERRLOG3; + break; + case ERR_LOG4: + offset = OBSERVER_0_ERRLOG4; + break; + case ERR_LOG5: + offset = OBSERVER_0_ERRLOG5; + break; + case ERR_LOG6: + offset = OBSERVER_0_ERRLOG6; + break; + case ERR_LOG7: + offset = OBSERVER_0_ERRLOG7; + break; + case ERR_LOG8: + offset = OBSERVER_0_ERRLOG8; + break; + case STALLEN: + offset = OBSERVER_0_STALLEN; + break; + default: + pr_alert("Invalid observer error register; reg num:%u\n", + err_log); + } + return offset; +} + +static void decode_gld_errlog5(struct msm_gladiator_data *msm_gld_data) +{ + unsigned int errtype; + u32 err_reg0, err_reg5; + + err_reg0 = readl_relaxed(msm_gld_data->gladiator_virt_base + + get_gld_offset(ERR_LOG0)); + err_reg5 = readl_relaxed(msm_gld_data->gladiator_virt_base + + get_gld_offset(ERR_LOG5)); + + errtype = (err_reg0 & GLD_ERROR_TYPE_MASK) >> GLD_ERROR_TYPE_SHIFT; + if (errtype == 3) + decode_gld_logged_error(err_reg5); + else if (errtype == 0 || errtype == 1) + pr_alert("Lower 32-bits of user: %08x\n", err_reg5); + else + pr_alert("Error type: Unknown; value:%u\n", errtype); +} + +static irqreturn_t msm_gladiator_isr(int irq, void *dev_id) +{ + u32 err_reg; + unsigned int err_log; + + struct msm_gladiator_data *msm_gld_data = dev_id; + + /* Check validity */ + bool gld_err_valid = readl_relaxed(msm_gld_data->gladiator_virt_base + + GLADIATOR_ERRVLD); + + bool obsrv_err_valid = readl_relaxed( + msm_gld_data->gladiator_virt_base + OBSERVER_0_ERRVLD); + + if (!gld_err_valid && !obsrv_err_valid) { + pr_err("%s Invalid Gladiator error reported, clear it\n", + __func__); + /* Clear IRQ */ + clear_gladiator_error(msm_gld_data->gladiator_virt_base); + return IRQ_HANDLED; + } + pr_alert("GLADIATOR ERROR DETECTED\n"); + if (gld_err_valid) { + pr_alert("GLADIATOR error log register data:\n"); + for (err_log = ERR_LOG0; err_log <= ERR_LOG8; err_log++) { + /* skip log register 7 as its reserved */ + if (err_log == ERR_LOG7) + continue; + if (err_log == ERR_LOG5) { + decode_gld_errlog5(msm_gld_data); + continue; + } + err_reg = readl_relaxed( + msm_gld_data->gladiator_virt_base + + get_gld_offset(err_log)); + decode_gld_errlog(err_reg, err_log); + } + } + if (obsrv_err_valid) { + pr_alert("Observor error log register data:\n"); + for (err_log = ERR_LOG0; err_log <= STALLEN; err_log++) { + /* skip log register 2, 6 and 8 as they are reserved */ + if ((err_log == ERR_LOG2) || (err_log == ERR_LOG6) + || (err_log == ERR_LOG8)) + continue; + err_reg = readl_relaxed( + msm_gld_data->gladiator_virt_base + + get_obs_offset(err_log)); + decode_obs_errlog(err_reg, err_log); + } + } + /* Clear IRQ */ + clear_gladiator_error(msm_gld_data->gladiator_virt_base); + if (enable_panic_on_error) + panic("Gladiator Cache Interconnect Error Detected!\n"); + else + WARN(1, "Gladiator Cache Interconnect Error Detected\n"); + + return IRQ_HANDLED; +} + +static const struct of_device_id gladiator_erp_v2_match_table[] = { + { .compatible = "qcom,msm-gladiator-v2" }, + {}, +}; + +static int parse_dt_node(struct platform_device *pdev, + struct msm_gladiator_data *msm_gld_data) +{ + int ret = 0; + struct resource *res; + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "gladiator_base"); + if (!res) + return -ENODEV; + if (!devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), + "msm-gladiator-erp")) { + + dev_err(&pdev->dev, "%s cannot reserve gladiator erp region\n", + __func__); + return -ENXIO; + } + msm_gld_data->gladiator_virt_base = devm_ioremap(&pdev->dev, + res->start, resource_size(res)); + if (!msm_gld_data->gladiator_virt_base) { + dev_err(&pdev->dev, "%s cannot map gladiator register space\n", + __func__); + return -ENXIO; + } + msm_gld_data->erp_irq = platform_get_irq(pdev, 0); + if (!msm_gld_data->erp_irq) + return -ENODEV; + + /* clear existing errors before enabling the interrupt */ + clear_gladiator_error(msm_gld_data->gladiator_virt_base); + ret = devm_request_irq(&pdev->dev, msm_gld_data->erp_irq, + msm_gladiator_isr, IRQF_TRIGGER_HIGH, + "gladiator-error", msm_gld_data); + if (ret) + dev_err(&pdev->dev, "Failed to register irq handler\n"); + + return ret; +} + +static inline void gladiator_irq_init(void __iomem *gladiator_virt_base) +{ + writel_relaxed(1, gladiator_virt_base + GLADIATOR_FAULTEN); + writel_relaxed(1, gladiator_virt_base + OBSERVER_0_FAULTEN); +} + +#define CCI_LEVEL 2 +static int gladiator_erp_pm_callback(struct notifier_block *nb, + unsigned long val, void *data) +{ + unsigned int level = (unsigned long) data; + struct msm_gladiator_data *msm_gld_data = container_of(nb, + struct msm_gladiator_data, pm_notifier_block); + + if (level != CCI_LEVEL) + return NOTIFY_DONE; + + switch (val) { + case CPU_CLUSTER_PM_EXIT: + gladiator_irq_init(msm_gld_data->gladiator_virt_base); + break; + default: + return NOTIFY_DONE; + } + + return NOTIFY_OK; +} + +static int gladiator_erp_v2_probe(struct platform_device *pdev) +{ + int ret; + struct msm_gladiator_data *msm_gld_data; + + msm_gld_data = devm_kzalloc(&pdev->dev, + sizeof(struct msm_gladiator_data), GFP_KERNEL); + if (!msm_gld_data) { + ret = -ENOMEM; + goto bail; + } + + ret = parse_dt_node(pdev, msm_gld_data); + if (ret) + goto bail; + msm_gld_data->pm_notifier_block.notifier_call = + gladiator_erp_pm_callback; + + if (of_property_match_string(pdev->dev.of_node, + "clock-names", "atb_clk") >= 0) { + msm_gld_data->qdss_clk = devm_clk_get(&pdev->dev, "atb_clk"); + if (IS_ERR(msm_gld_data->qdss_clk)) { + dev_err(&pdev->dev, "Failed to get QDSS ATB clock\n"); + goto bail; + } + } else { + dev_err(&pdev->dev, "No matching string of QDSS ATB clock\n"); + goto bail; + } + + ret = clk_prepare_enable(msm_gld_data->qdss_clk); + if (ret) + goto err_atb_clk; + + gladiator_irq_init(msm_gld_data->gladiator_virt_base); + platform_set_drvdata(pdev, msm_gld_data); + cpu_pm_register_notifier(&msm_gld_data->pm_notifier_block); +#ifdef CONFIG_PANIC_ON_GLADIATOR_ERROR_V2 + enable_panic_on_error = 1; +#endif + dev_info(&pdev->dev, "MSM Gladiator Error Reporting V2 Initialized\n"); + return ret; + +err_atb_clk: + clk_disable_unprepare(msm_gld_data->qdss_clk); + +bail: + dev_err(&pdev->dev, "Probe failed bailing out\n"); + return ret; +} + +static int gladiator_erp_v2_remove(struct platform_device *pdev) +{ + struct msm_gladiator_data *msm_gld_data = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + cpu_pm_unregister_notifier(&msm_gld_data->pm_notifier_block); + clk_disable_unprepare(msm_gld_data->qdss_clk); + return 0; +} + +static struct platform_driver gladiator_erp_driver = { + .probe = gladiator_erp_v2_probe, + .remove = gladiator_erp_v2_remove, + .driver = { + .name = MODULE_NAME, + .owner = THIS_MODULE, + .of_match_table = gladiator_erp_v2_match_table, + }, +}; + +static int __init init_gladiator_erp_v2(void) +{ + int ret; + + ret = scm_is_secure_device(); + if (ret == 0) { + pr_info("Gladiator Error Reporting not available\n"); + return -ENODEV; + } + + return platform_driver_register(&gladiator_erp_driver); +} +module_init(init_gladiator_erp_v2); + +static void __exit exit_gladiator_erp_v2(void) +{ + return platform_driver_unregister(&gladiator_erp_driver); +} +module_exit(exit_gladiator_erp_v2); + +MODULE_DESCRIPTION("Gladiator Error Reporting V2"); +MODULE_LICENSE("GPL v2"); |