msm: cache_erp: Dump I/O regions on cache errors

Support dumping arbitrary I/O register regions in response
to soft cache errors to facilitate identifying the cause of
the soft errors.

Change-Id: I4ccb720faecb0c7c1227e37234b9310a3d34f215
Signed-off-by: Stepan Moskovchenko <stepanm@codeaurora.org>
diff --git a/arch/arm/mach-msm/cache_erp.c b/arch/arm/mach-msm/cache_erp.c
index ddea91c..f52bc28 100644
--- a/arch/arm/mach-msm/cache_erp.c
+++ b/arch/arm/mach-msm/cache_erp.c
@@ -123,11 +123,18 @@
 	unsigned int mplxrexnok;
 };
 
+struct msm_erp_dump_region {
+	struct resource *res;
+	void __iomem *va;
+};
+
 static DEFINE_PER_CPU(struct msm_l1_err_stats, msm_l1_erp_stats);
 static struct msm_l2_err_stats msm_l2_erp_stats;
 
 static int l1_erp_irq, l2_erp_irq;
 static struct proc_dir_entry *procfs_entry;
+static int num_dump_regions;
+static struct msm_erp_dump_region *dump_regions;
 
 #ifdef CONFIG_MSM_L1_ERR_LOG
 static struct proc_dir_entry *procfs_log_entry;
@@ -211,6 +218,22 @@
 	return len;
 }
 
+static int msm_erp_dump_regions(void)
+{
+	int i = 0;
+	struct msm_erp_dump_region *r;
+
+	for (i = 0; i < num_dump_regions; i++) {
+		r = &dump_regions[i];
+
+		pr_alert("%s %pR:\n", r->res->name, r->res);
+		print_hex_dump(KERN_ALERT, "", DUMP_PREFIX_OFFSET, 32, 4, r->va,
+			       resource_size(r->res), 0);
+	}
+
+	return 0;
+}
+
 #ifdef CONFIG_MSM_L1_ERR_LOG
 static int proc_read_log(char *page, char **start, off_t off, int count,
 	int *eof, void *data)
@@ -267,6 +290,7 @@
 		pr_alert("\tCESR      = 0x%08x\n", cesr);
 		pr_alert("\tCPU speed = %lu\n", acpuclk_get_rate(cpu));
 		pr_alert("\tMIDR      = 0x%08x\n", read_cpuid_id());
+		msm_erp_dump_regions();
 	}
 
 	if (cesr & CESR_DCTPE) {
@@ -425,6 +449,9 @@
 	if (port_error && print_alert)
 		ERP_PORT_ERR("L2 master port error detected");
 
+	if (soft_error && print_alert)
+		msm_erp_dump_regions();
+
 	if (soft_error && !unrecoverable)
 		ERP_1BIT_ERR("L2 single-bit error detected");
 
@@ -464,6 +491,37 @@
 	.notifier_call = cache_erp_cpu_callback,
 };
 
+static int msm_erp_read_dump_regions(struct platform_device *pdev)
+{
+	int i;
+	struct device_node *np = pdev->dev.of_node;
+	struct resource *res;
+
+	num_dump_regions = of_property_count_strings(np, "reg-names");
+
+	if (num_dump_regions <= 0) {
+		num_dump_regions = 0;
+		return 0; /* Not an error - this is an optional property */
+	}
+
+	dump_regions = devm_kzalloc(&pdev->dev,
+				    sizeof(*dump_regions) * num_dump_regions,
+				    GFP_KERNEL);
+	if (!dump_regions)
+		return -ENOMEM;
+
+	for (i = 0; i < num_dump_regions; i++) {
+		res = platform_get_resource(pdev, IORESOURCE_MEM, i);
+		dump_regions[i].res = res;
+		dump_regions[i].va = devm_ioremap(&pdev->dev, res->start,
+						  resource_size(res));
+		if (!dump_regions[i].va)
+			return -ENOMEM;
+	}
+
+	return 0;
+}
+
 static int msm_cache_erp_probe(struct platform_device *pdev)
 {
 	struct resource *r;
@@ -511,6 +569,11 @@
 		goto fail_l2;
 	}
 
+	ret = msm_erp_read_dump_regions(pdev);
+
+	if (ret)
+		goto fail_l2;
+
 	get_online_cpus();
 	register_hotcpu_notifier(&cache_erp_cpu_notifier);
 	for_each_cpu(cpu, cpu_online_mask)