summaryrefslogtreecommitdiff
path: root/arch/arm/mach-ux500/dbx500_dump.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-ux500/dbx500_dump.c')
-rw-r--r--arch/arm/mach-ux500/dbx500_dump.c233
1 files changed, 233 insertions, 0 deletions
diff --git a/arch/arm/mach-ux500/dbx500_dump.c b/arch/arm/mach-ux500/dbx500_dump.c
new file mode 100644
index 00000000000..89470216590
--- /dev/null
+++ b/arch/arm/mach-ux500/dbx500_dump.c
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * License Terms: GNU General Public License v2
+ * Author: Johan Bjornstedt <johan.bjornstedt@stericsson.com>
+ *
+ * Save DBx500 registers in case of kernel crash
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/kdebug.h>
+
+#include <mach/hardware.h>
+#include <mach/db8500-regs.h>
+#include <mach/db5500-regs.h>
+
+struct dbx500_dump_info {
+ char *name;
+ int *data;
+ int *io_addr;
+ int phy_addr;
+ int size;
+};
+
+static struct dbx500_dump_info db8500_dump[] = {
+ {
+ .name = "prcmu_tcdm",
+ .phy_addr = U8500_PRCMU_TCDM_BASE,
+ .size = 0x1000,
+ },
+ {
+ .name = "prcmu_non_sec_1",
+ .phy_addr = U8500_PRCMU_BASE,
+ .size = 0x340,
+ },
+ {
+ .name = "prcmu_pmb",
+ .phy_addr = (U8500_PRCMU_BASE + 0x344),
+ .size = 0xC,
+ },
+ {
+ .name = "prcmu_thermal",
+ .phy_addr = (U8500_PRCMU_BASE + 0x3C0),
+ .size = 0x40,
+ },
+ {
+ .name = "prcmu_non_sec_2",
+ .phy_addr = (U8500_PRCMU_BASE + 0x404),
+ .size = 0x1FC,
+ },
+ {
+ .name = "prcmu_icn_pmu",
+ .phy_addr = (U8500_PRCMU_BASE + 0xE00),
+ .size = 0x90,
+ },
+ {
+ .name = "db8500_fuses",
+ .phy_addr = (U8500_BACKUPRAM1_BASE + 0xF70),
+ .size = 0xc,
+ },
+};
+
+static struct dbx500_dump_info db9540_dump[] = {
+ {
+ .name = "prcmu_tcdm",
+ .phy_addr = U9540_PRCMU_TCDM_BASE,
+ .size = 0x1000,
+ },
+ {
+ .name = "prcmu_non_sec_1",
+ .phy_addr = U8500_PRCMU_BASE,
+ .size = 0x340,
+ },
+ {
+ .name = "prcmu_pmb",
+ .phy_addr = (U8500_PRCMU_BASE + 0x344),
+ .size = 0xC,
+ },
+ {
+ .name = "prcmu_thermal",
+ .phy_addr = (U8500_PRCMU_BASE + 0x3C0),
+ .size = 0x40,
+ },
+ {
+ .name = "prcmu_non_sec_2",
+ .phy_addr = (U8500_PRCMU_BASE + 0x404),
+ .size = 0x1FC,
+ },
+ {
+ .name = "prcmu_icn_pmu",
+ .phy_addr = (U8500_PRCMU_BASE + 0xE00),
+ .size = 0x118,
+ },
+};
+
+static struct dbx500_dump_info db5500_dump[] = {
+ {
+ .name = "prcmu_tcdm",
+ .phy_addr = U5500_PRCMU_TCDM_BASE,
+ .size = 0x5000,
+ },
+ {
+ .name = "prcmu_gpio",
+ .phy_addr = U5500_GPIO2_BASE,
+ .size = 0x1000,
+ },
+ {
+ .name = "prcmu_msp1",
+ .phy_addr = U5500_MSP1_BASE,
+ .size = 0x1000,
+ },
+ {
+ .name = "prcmu_sec",
+ .phy_addr = (U5500_PRCMU_BASE + 0x1000),
+ .size = 0x1000,
+ },
+ {
+ .name = "prcmu_unsec",
+ .phy_addr = U5500_PRCMU_BASE,
+ .size = 0x1000,
+ },
+};
+
+static struct dbx500_dump_info *dbx500_dump;
+static int dbx500_dump_size;
+static bool dbx500_dump_done;
+
+static DEFINE_SPINLOCK(dbx500_dump_lock);
+
+static int crash_notifier(struct notifier_block *nb, unsigned long val,
+ void *data)
+{
+ int i;
+ unsigned long flags;
+
+ /*
+ * Since there are two ways into this function (die and panic) we have
+ * to make sure we only dump the DB registers once
+ */
+ spin_lock_irqsave(&dbx500_dump_lock, flags);
+ if (dbx500_dump_done)
+ return NOTIFY_DONE;
+
+ pr_info("dbx500_dump notified of crash\n");
+
+ for (i = 0; i < dbx500_dump_size; i++) {
+ memcpy_fromio(dbx500_dump[i].data, dbx500_dump[i].io_addr,
+ dbx500_dump[i].size);
+ }
+
+ dbx500_dump_done = true;
+ spin_unlock_irqrestore(&dbx500_dump_lock, flags);
+
+ return NOTIFY_DONE;
+}
+
+static void __init init_io_addresses(void)
+{
+ int i;
+
+ for (i = 0; i < dbx500_dump_size; i++)
+ dbx500_dump[i].io_addr = ioremap(dbx500_dump[i].phy_addr,
+ dbx500_dump[i].size);
+}
+
+static struct notifier_block die_notifier = {
+ .notifier_call = crash_notifier,
+};
+
+static struct notifier_block panic_notifier = {
+ .notifier_call = crash_notifier,
+};
+
+int __init dbx500_dump_init(void)
+{
+ int err, i;
+
+ if (cpu_is_u5500()) {
+ dbx500_dump = db5500_dump;
+ dbx500_dump_size = ARRAY_SIZE(db5500_dump);
+ } else if (cpu_is_u8500()) {
+ dbx500_dump = db8500_dump;
+ dbx500_dump_size = ARRAY_SIZE(db8500_dump);
+ } else if (cpu_is_u9540()) {
+ dbx500_dump = db9540_dump;
+ dbx500_dump_size = ARRAY_SIZE(db9540_dump);
+ } else {
+ ux500_unknown_soc();
+ }
+
+ for (i = 0; i < dbx500_dump_size; i++) {
+ dbx500_dump[i].data = kmalloc(dbx500_dump[i].size, GFP_KERNEL);
+ if (!dbx500_dump[i].data) {
+ pr_err("dbx500_dump: Could not allocate memory for "
+ "%s\n", dbx500_dump[i].name);
+ err = -ENOMEM;
+ goto free_mem;
+ }
+ }
+
+ init_io_addresses();
+
+ err = atomic_notifier_chain_register(&panic_notifier_list,
+ &panic_notifier);
+ if (err != 0) {
+ pr_err("dbx500_dump: Unable to register a panic notifier %d\n",
+ err);
+ goto free_mem;
+ }
+
+ err = register_die_notifier(&die_notifier);
+ if (err != 0) {
+ pr_err("dbx500_dump: Unable to register a die notifier %d\n",
+ err);
+ goto free_panic_notifier;
+ }
+ pr_info("dbx500_dump: driver initialized\n");
+ return err;
+
+free_panic_notifier:
+ atomic_notifier_chain_unregister(&panic_notifier_list,
+ &panic_notifier);
+
+free_mem:
+ for (i = i - 1; i >= 0; i--)
+ kfree(dbx500_dump[i].data);
+
+ return err;
+}
+arch_initcall(dbx500_dump_init);