/* * Copyright (c) 2015 Samsung Electronics Co., Ltd. * http://www.samsung.com/ * * Exynos - Support SoC specific Reboot * Author: Hosung Kim * * 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 #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_EXYNOS_ACPM #include #endif #include extern void (*arm_pm_restart)(enum reboot_mode reboot_mode, const char *cmd); static void __iomem *exynos_pmu_base = NULL; static const char * const big_cores[] = { "arm,mongoose", "arm,meerkat", "arm,cortex-a73", NULL, }; static bool is_big_cpu(struct device_node *cn) { const char * const *lc; for (lc = big_cores; *lc; lc++) if (of_device_is_compatible(cn, *lc)) return true; return false; } int soc_has_big(void) { struct device_node *cn = NULL; u32 big_cpu_cnt = 0; /* find arm,mongoose compatable in device tree */ while ((cn = of_find_node_by_type(cn, "cpu"))) { if (is_big_cpu(cn)) big_cpu_cnt++; } return big_cpu_cnt; } #define CPU_RESET_DISABLE_FROM_SOFTRESET (0x041C) #define CPU_RESET_DISABLE_FROM_WDTRESET (0x0414) #define BIG_CPU0_RESET (0x220C) #define BIG_NONCPU_ETC_RESET (0x242C) #define PMU_CPU_OFFSET (0x80) #define SWRESET (0x0400) #define RESET_SEQUENCER_CONFIGURATION (0x0500) #define PS_HOLD_CONTROL (0x330C) #define EXYNOS_PMU_SYSIP_DAT0 (0x0810) /* defines for BIG reset */ #define PEND_BIG (1 << 0) #define PEND_LITTLE (1 << 1) #define DEFAULT_VAL_CPU_RESET_DISABLE (0xFFFFFFFC) #define RESET_DISABLE_GPR_CPUPORESET (1 << 15) #define RESET_DISABLE_WDT_CPUPORESET (1 << 12) #define RESET_DISABLE_CORERESET (1 << 9) #define RESET_DISABLE_CPUPORESET (1 << 8) #define RESET_DISABLE_WDT_L2RESET (1 << 31) #define RESET_DISABLE_WDT_PRESET_DBG (1 << 25) #define RESET_DISABLE_PRESET_DBG (1 << 18) #define RESET_DISABLE_L2RESET (1 << 16) #define DFD_EDPCSR_DUMP_EN (1 << 0) #define DFD_L2RSTDISABLE_BIG_EN (1 << 11) #define DFD_DBGL1RSTDISABLE_BIG_EN (1 << 10) #define DFD_L2RSTDISABLE_LITTLE_EN (1 << 9) #define DFD_DBGL1RSTDISABLE_LITTLE_EN (1 << 8) #define DFD_CLEAR_L2RSTDISABLE_BIG (1 << 7) #define DFD_CLEAR_DBGL1RSTDISABLE_BIG (1 << 6) #define DFD_CLEAR_L2RSTDISABLE_LITTLE (1 << 5) #define DFD_CLEAR_DBGL1RSTDISABLE_LITTLE (1 << 4) #define DFD_L2RSTDISABLE (DFD_L2RSTDISABLE_BIG_EN \ | DFD_DBGL1RSTDISABLE_BIG_EN \ | DFD_L2RSTDISABLE_LITTLE_EN \ | DFD_DBGL1RSTDISABLE_LITTLE_EN) #define DFD_CLEAR_L2RSTDISABLE (DFD_CLEAR_L2RSTDISABLE_BIG \ | DFD_CLEAR_DBGL1RSTDISABLE_BIG \ | DFD_CLEAR_L2RSTDISABLE_LITTLE \ | DFD_CLEAR_DBGL1RSTDISABLE_LITTLE) #define DFD_RESET_PEND (PEND_BIG | PEND_LITTLE) #define DFD_DISABLE_RESET (RESET_DISABLE_WDT_CPUPORESET \ | RESET_DISABLE_CORERESET \ | RESET_DISABLE_CPUPORESET) #define DFD_BIG_NONCPU_ETC_RESET (RESET_DISABLE_WDT_L2RESET \ | RESET_DISABLE_WDT_PRESET_DBG \ | RESET_DISABLE_PRESET_DBG \ | RESET_DISABLE_L2RESET) static void dfd_set_dump_gpr(int en) { u32 reg_val; if (en) { reg_val = DFD_EDPCSR_DUMP_EN | DFD_L2RSTDISABLE; exynos_pmu_write(RESET_SEQUENCER_CONFIGURATION, reg_val); } else { exynos_pmu_read(RESET_SEQUENCER_CONFIGURATION, ®_val); if (reg_val) { reg_val = DFD_EDPCSR_DUMP_EN | DFD_CLEAR_L2RSTDISABLE; exynos_pmu_write(RESET_SEQUENCER_CONFIGURATION, reg_val); } } #ifdef REBOOT_DEBUG exynos_pmu_read(RESET_SEQUENCER_CONFIGURATION, ®_val); pr_info("RESET_SEQUENCER_CONFIGURATION :%x\n", reg_val); #endif } void big_reset_control(int en) { u32 reg_val, val; u32 big_cpu_cnt = soc_has_big(); u32 check_dumpGPR; if (big_cpu_cnt == 0 || !exynos_pmu_base) return; exynos_pmu_read(RESET_SEQUENCER_CONFIGURATION, &check_dumpGPR); if (!(check_dumpGPR & DFD_EDPCSR_DUMP_EN)) return; if (en) { /* reset disable for BIG */ exynos_pmu_read(CPU_RESET_DISABLE_FROM_SOFTRESET, ®_val); if (reg_val != DEFAULT_VAL_CPU_RESET_DISABLE) exynos_pmu_update(CPU_RESET_DISABLE_FROM_SOFTRESET, DFD_RESET_PEND, 0); exynos_pmu_read(CPU_RESET_DISABLE_FROM_WDTRESET, ®_val); if (reg_val != DEFAULT_VAL_CPU_RESET_DISABLE) exynos_pmu_update(CPU_RESET_DISABLE_FROM_WDTRESET, DFD_RESET_PEND, 0); /* The reset disable of BIG core and BIG cluster in Exynos9610(Artemis) * should be skipped because of cache flush in dump gpr situation */ /* for (val = 0; val < big_cpu_cnt; val++) exynos_pmu_update(BIG_CPU0_RESET + (val * PMU_CPU_OFFSET), DFD_DISABLE_RESET, DFD_DISABLE_RESET); exynos_pmu_update(BIG_NONCPU_ETC_RESET, DFD_BIG_NONCPU_ETC_RESET, DFD_BIG_NONCPU_ETC_RESET); */ } else { /* reset enable for BIG */ for (val = 0; val < big_cpu_cnt; val++) exynos_pmu_update(BIG_CPU0_RESET + (val * PMU_CPU_OFFSET), DFD_DISABLE_RESET, 0); exynos_pmu_update(BIG_NONCPU_ETC_RESET, DFD_BIG_NONCPU_ETC_RESET, 0); } #ifdef REBOOT_DEBUG exynos_pmu_read(CPU_RESET_DISABLE_FROM_SOFTRESET, ®_val); pr_info("CPU_RESET_DISABLE_FROM_SOFTRESET :%x\n", reg_val); exynos_pmu_read(CPU_RESET_DISABLE_FROM_WDTRESET, ®_val); pr_info("CPU_RESET_DISABLE_FROM_WDTRESET :%x\n", reg_val); exynos_pmu_read(BIG_NONCPU_ETC_RESET, ®_val); pr_info("BIG_NONCPU_ETC_RESET :%x\n", reg_val); for (val = 0; val < big_cpu_cnt; val++) { exynos_pmu_read(BIG_CPU0_RESET + (val * PMU_CPU_OFFSET), ®_val); pr_info("BIG_CPU%d_RESET :%x\n", val, reg_val); } #endif } #define INFORM_NONE 0x0 #define INFORM_RAMDUMP 0xd #define INFORM_RECOVERY 0xf #define REBOOT_MODE_NORMAL 0x00 /* Reboot into fastboot mode */ #define REBOOT_MODE_FASTBOOT 0xFC /* Reboot into recovery */ #define REBOOT_MODE_RECOVERY 0xFF #if !defined(CONFIG_SEC_REBOOT) #ifdef CONFIG_OF static void exynos_power_off(void) { int poweroff_try = 0; int power_gpio = -1; unsigned int keycode = 0; struct device_node *np, *pp; np = of_find_node_by_path("/gpio_keys"); if (!np) return; for_each_child_of_node(np, pp) { if (!of_find_property(pp, "gpios", NULL)) continue; of_property_read_u32(pp, "linux,code", &keycode); if (keycode == KEY_POWER) { pr_info("%s: <%u>\n", __func__, keycode); power_gpio = of_get_gpio(pp, 0); break; } } of_node_put(np); if (!gpio_is_valid(power_gpio)) { pr_err("Couldn't find power key node\n"); return; } while (1) { /* wait for power button release */ if (gpio_get_value(power_gpio)) { #ifdef CONFIG_EXYNOS_ACPM exynos_acpm_reboot(); #endif pr_emerg("%s: Set PS_HOLD Low.\n", __func__); writel(readl(exynos_pmu_base + PS_HOLD_CONTROL) & 0xFFFFFEFF, exynos_pmu_base + PS_HOLD_CONTROL); ++poweroff_try; pr_emerg("%s: Should not reach here! (poweroff_try:%d)\n", __func__, poweroff_try); } else { /* if power button is not released, wait and check TA again */ pr_info("%s: PowerButton is not released.\n", __func__); } mdelay(1000); } } #else static void exynos_power_off(void) { pr_info("Exynos power off does not support.\n"); } #endif #endif static void exynos_reboot(enum reboot_mode mode, const char *cmd) { u32 soc_id, revision; void __iomem *addr; if (!exynos_pmu_base) return; #ifdef CONFIG_EXYNOS_ACPM exynos_acpm_reboot(); #endif printk("[%s] reboot cmd: %s\n", __func__, cmd); addr = exynos_pmu_base + EXYNOS_PMU_SYSIP_DAT0; if (cmd) { if (!strcmp(cmd, "bootloader") || !strcmp(cmd, "bl") || !strcmp((char *)cmd, "fastboot") || !strcmp(cmd, "fb")) { __raw_writel(REBOOT_MODE_FASTBOOT, addr); } else if (!strcmp(cmd, "recovery")) { __raw_writel(REBOOT_MODE_RECOVERY, addr); } } /* Check by each SoC */ soc_id = exynos_soc_info.product_id; revision = exynos_soc_info.revision; pr_info("SOC ID %X. Revision: %x\n", soc_id, revision); switch(soc_id) { case EXYNOS9810_SOC_ID: /* Check reset_sequencer_configuration register */ if (readl(exynos_pmu_base + RESET_SEQUENCER_CONFIGURATION) & DFD_EDPCSR_DUMP_EN) { dfd_set_dump_gpr(0); } if (revision < EXYNOS_MAIN_REV_1) { pr_emerg("%s: Exynos SoC reset right now with fake watchdog\n", __func__); s3c2410wdt_set_emergency_reset(1000, 1); while (1) wfi(); } break; case EXYNOS9610_SOC_ID: /* Check reset_sequencer_configuration register */ if (readl(exynos_pmu_base + RESET_SEQUENCER_CONFIGURATION) & DFD_EDPCSR_DUMP_EN) { dfd_set_dump_gpr(0); } break; default: break; } /* Do S/W Reset */ pr_emerg("%s: Exynos SoC reset right now\n", __func__); __raw_writel(0x1, exynos_pmu_base + SWRESET); } static int __init exynos_reboot_setup(struct device_node *np) { int err = 0; u32 id; if (!of_property_read_u32(np, "pmu_base", &id)) { exynos_pmu_base = ioremap(id, SZ_16K); if (!exynos_pmu_base) { pr_err("%s: failed to map to exynos-pmu-base address 0x%x\n", __func__, id); err = -ENOMEM; } } of_node_put(np); pr_info("[Exynos Reboot]: Success to register arm_pm_restart\n"); big_reset_control(0); return err; } static const struct of_device_id reboot_of_match[] __initconst = { { .compatible = "exynos,reboot", .data = exynos_reboot_setup}, {}, }; typedef int (*reboot_initcall_t)(const struct device_node *); static int __init exynos_reboot_init(void) { struct device_node *np; const struct of_device_id *matched_np; reboot_initcall_t init_fn; np = of_find_matching_node_and_match(NULL, reboot_of_match, &matched_np); if (!np) return -ENODEV; arm_pm_restart = exynos_reboot; #if !defined(CONFIG_SEC_REBOOT) pm_power_off = exynos_power_off; #endif init_fn = (reboot_initcall_t)matched_np->data; return init_fn(np); } subsys_initcall(exynos_reboot_init);