Loading drivers/power/reset/Kconfig +19 −6 Original line number Diff line number Diff line Loading @@ -98,12 +98,6 @@ config POWER_RESET_IMX say N here or disable in dts to make sure pm_power_off never be overwrote wrongly by this driver. config POWER_RESET_MSM bool "Qualcomm MSM power-off driver" depends on ARCH_QCOM help Power off and restart support for Qualcomm boards. config POWER_RESET_LTC2952 bool "LTC2952 PowerPath power-off driver" depends on OF_GPIO Loading @@ -111,6 +105,25 @@ config POWER_RESET_LTC2952 This driver supports an external powerdown trigger and board power down via the LTC2952. Bindings are made in the device tree. config POWER_RESET_QCOM bool "Qualcomm Technologies, Inc. MSM power-off driver" depends on ARCH_MSM || ARCH_QCOM depends on POWER_RESET help Power off and restart support for Qualcomm Technologies, Inc. boards. This driver supports board power down and restart using PMIC PS_HOLD. Say Y here if you have a Qualcomm Tecnologies, Inc. board and wish to enable restart driver. config QCOM_DLOAD_MODE bool "Qualcomm Technologies, Inc. download mode" depends on POWER_RESET_QCOM help This makes the SoC enter download mode when it resets due to a kernel panic. Note that this doesn't by itself make the kernel reboot on a kernel panic - that must be enabled via another mechanism. config POWER_RESET_QNAP bool "QNAP power-off driver" depends on OF_GPIO && PLAT_ORION Loading drivers/power/reset/Makefile +1 −1 Original line number Diff line number Diff line Loading @@ -9,7 +9,7 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o obj-$(CONFIG_POWER_RESET_IMX) += imx-snvs-poweroff.o obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o obj-$(CONFIG_POWER_RESET_QCOM) += msm-poweroff.o obj-$(CONFIG_POWER_RESET_LTC2952) += ltc2952-poweroff.o obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o Loading drivers/power/reset/msm-poweroff.c +421 −13 Original line number Diff line number Diff line /* Copyright (c) 2013, The Linux Foundation. All rights reserved. /* Copyright (c) 2013-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 Loading @@ -15,49 +15,457 @@ #include <linux/err.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/io.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/module.h> #include <linux/reboot.h> #include <linux/pm.h> #include <linux/delay.h> #include <linux/qpnp/power-on.h> #include <linux/of_address.h> #include <asm/cacheflush.h> #include <asm/system_misc.h> #include <soc/qcom/scm.h> #include <soc/qcom/restart.h> #include <soc/qcom/watchdog.h> #define EMERGENCY_DLOAD_MAGIC1 0x322A4F99 #define EMERGENCY_DLOAD_MAGIC2 0xC67E4350 #define EMERGENCY_DLOAD_MAGIC3 0x77777777 #define SCM_IO_DISABLE_PMIC_ARBITER 1 #define SCM_IO_DEASSERT_PS_HOLD 2 #define SCM_WDOG_DEBUG_BOOT_PART 0x9 #define SCM_DLOAD_MODE 0X10 #define SCM_EDLOAD_MODE 0X01 #define SCM_DLOAD_CMD 0x10 static int restart_mode; void *restart_reason; static bool scm_pmic_arbiter_disable_supported; static bool scm_deassert_ps_hold_supported; /* Download mode master kill-switch */ static void __iomem *msm_ps_hold; static int do_msm_restart(struct notifier_block *nb, unsigned long action, void *data) { writel(0, msm_ps_hold); mdelay(10000); static phys_addr_t tcsr_boot_misc_detect; #ifdef CONFIG_QCOM_DLOAD_MODE #define EDL_MODE_PROP "qcom,msm-imem-emergency_download_mode" #define DL_MODE_PROP "qcom,msm-imem-download_mode" static int in_panic; static void *dload_mode_addr; static bool dload_mode_enabled; static void *emergency_dload_mode_addr; static bool scm_dload_supported; static int dload_set(const char *val, struct kernel_param *kp); static int download_mode = 1; module_param_call(download_mode, dload_set, param_get_int, &download_mode, 0644); static int panic_prep_restart(struct notifier_block *this, unsigned long event, void *ptr) { in_panic = 1; return NOTIFY_DONE; } static struct notifier_block restart_nb = { .notifier_call = do_msm_restart, .priority = 128, static struct notifier_block panic_blk = { .notifier_call = panic_prep_restart, }; int scm_set_dload_mode(int arg1, int arg2) { struct scm_desc desc = { .args[0] = arg1, .args[1] = arg2, .arginfo = SCM_ARGS(2), }; if (!scm_dload_supported) { if (tcsr_boot_misc_detect) return scm_io_write(tcsr_boot_misc_detect, arg1); return 0; } if (!is_scm_armv8()) return scm_call_atomic2(SCM_SVC_BOOT, SCM_DLOAD_CMD, arg1, arg2); return scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_BOOT, SCM_DLOAD_CMD), &desc); } static void set_dload_mode(int on) { int ret; if (dload_mode_addr) { __raw_writel(on ? 0xE47B337D : 0, dload_mode_addr); __raw_writel(on ? 0xCE14091A : 0, dload_mode_addr + sizeof(unsigned int)); /* Make sure the download cookie is updated */ mb(); } ret = scm_set_dload_mode(on ? SCM_DLOAD_MODE : 0, 0); if (ret) pr_err("Failed to set secure DLOAD mode: %d\n", ret); dload_mode_enabled = on; } static bool get_dload_mode(void) { return dload_mode_enabled; } static void enable_emergency_dload_mode(void) { int ret; if (emergency_dload_mode_addr) { __raw_writel(EMERGENCY_DLOAD_MAGIC1, emergency_dload_mode_addr); __raw_writel(EMERGENCY_DLOAD_MAGIC2, emergency_dload_mode_addr + sizeof(unsigned int)); __raw_writel(EMERGENCY_DLOAD_MAGIC3, emergency_dload_mode_addr + (2 * sizeof(unsigned int))); /* Need disable the pmic wdt, then the emergency dload mode * will not auto reset. */ qpnp_pon_wd_config(0); /* Make sure all the cookied are flushed to memory */ mb(); } ret = scm_set_dload_mode(SCM_EDLOAD_MODE, 0); if (ret) pr_err("Failed to set secure EDLOAD mode: %d\n", ret); } static int dload_set(const char *val, struct kernel_param *kp) { int ret; int old_val = download_mode; ret = param_set_int(val, kp); if (ret) return ret; /* If download_mode is not zero or one, ignore. */ if (download_mode >> 1) { download_mode = old_val; return -EINVAL; } set_dload_mode(download_mode); return 0; } #else #define set_dload_mode(x) do {} while (0) static void enable_emergency_dload_mode(void) { pr_err("dload mode is not enabled on target\n"); } static bool get_dload_mode(void) { return false; } #endif void msm_set_restart_mode(int mode) { restart_mode = mode; } EXPORT_SYMBOL(msm_set_restart_mode); /* * Force the SPMI PMIC arbiter to shutdown so that no more SPMI transactions * are sent from the MSM to the PMIC. This is required in order to avoid an * SPMI lockup on certain PMIC chips if PS_HOLD is lowered in the middle of * an SPMI transaction. */ static void halt_spmi_pmic_arbiter(void) { struct scm_desc desc = { .args[0] = 0, .arginfo = SCM_ARGS(1), }; if (scm_pmic_arbiter_disable_supported) { pr_crit("Calling SCM to disable SPMI PMIC arbiter\n"); if (!is_scm_armv8()) scm_call_atomic1(SCM_SVC_PWR, SCM_IO_DISABLE_PMIC_ARBITER, 0); else scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_PWR, SCM_IO_DISABLE_PMIC_ARBITER), &desc); } } static void msm_restart_prepare(const char *cmd) { #ifdef CONFIG_QCOM_DLOAD_MODE bool need_warm_reset = false; /* Write download mode flags if we're panic'ing * Write download mode flags if restart_mode says so * Kill download mode if master-kill switch is set */ set_dload_mode(download_mode && (in_panic || restart_mode == RESTART_DLOAD)); #endif if (qpnp_pon_check_hard_reset_stored()) { /* Set warm reset as true when device is in dload mode */ if (get_dload_mode() || ((cmd != NULL && cmd[0] != '\0') && !strcmp(cmd, "edl"))) need_warm_reset = true; } else { need_warm_reset = (get_dload_mode() || (cmd != NULL && cmd[0] != '\0')); } /* Hard reset the PMIC unless memory contents must be maintained. */ if (need_warm_reset) qpnp_pon_system_pwr_off(PON_POWER_OFF_WARM_RESET); else qpnp_pon_system_pwr_off(PON_POWER_OFF_HARD_RESET); if (cmd != NULL) { if (!strncmp(cmd, "bootloader", 10)) { qpnp_pon_set_restart_reason( PON_RESTART_REASON_BOOTLOADER); __raw_writel(0x77665500, restart_reason); } else if (!strncmp(cmd, "recovery", 8)) { qpnp_pon_set_restart_reason( PON_RESTART_REASON_RECOVERY); __raw_writel(0x77665502, restart_reason); } else if (!strcmp(cmd, "rtc")) { qpnp_pon_set_restart_reason( PON_RESTART_REASON_RTC); __raw_writel(0x77665503, restart_reason); } else if (!strcmp(cmd, "dm-verity device corrupted")) { qpnp_pon_set_restart_reason( PON_RESTART_REASON_DMVERITY_CORRUPTED); __raw_writel(0x77665508, restart_reason); } else if (!strcmp(cmd, "dm-verity enforcing")) { qpnp_pon_set_restart_reason( PON_RESTART_REASON_DMVERITY_ENFORCE); __raw_writel(0x77665509, restart_reason); } else if (!strcmp(cmd, "keys clear")) { qpnp_pon_set_restart_reason( PON_RESTART_REASON_KEYS_CLEAR); __raw_writel(0x7766550a, restart_reason); } else if (!strncmp(cmd, "oem-", 4)) { unsigned long code; int ret; ret = kstrtoul(cmd + 4, 16, &code); if (!ret) __raw_writel(0x6f656d00 | (code & 0xff), restart_reason); } else if (!strncmp(cmd, "edl", 3)) { enable_emergency_dload_mode(); } else { __raw_writel(0x77665501, restart_reason); } } flush_cache_all(); /*outer_flush_all is not supported by 64bit kernel*/ #ifndef CONFIG_ARM64 outer_flush_all(); #endif } /* * Deassert PS_HOLD to signal the PMIC that we are ready to power down or reset. * Do this by calling into the secure environment, if available, or by directly * writing to a hardware register. * * This function should never return. */ static void deassert_ps_hold(void) { struct scm_desc desc = { .args[0] = 0, .arginfo = SCM_ARGS(1), }; if (scm_deassert_ps_hold_supported) { /* This call will be available on ARMv8 only */ scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_PWR, SCM_IO_DEASSERT_PS_HOLD), &desc); } /* Fall-through to the direct write in case the scm_call "returns" */ __raw_writel(0, msm_ps_hold); } static void do_msm_restart(enum reboot_mode reboot_mode, const char *cmd) { int ret; struct scm_desc desc = { .args[0] = 1, .args[1] = 0, .arginfo = SCM_ARGS(2), }; pr_notice("Going down for restart now\n"); msm_restart_prepare(cmd); #ifdef CONFIG_QCOM_DLOAD_MODE /* * Trigger a watchdog bite here and if this fails, * device will take the usual restart path. */ if (WDOG_BITE_ON_PANIC && in_panic) msm_trigger_wdog_bite(); #endif /* Needed to bypass debug image on some chips */ if (!is_scm_armv8()) ret = scm_call_atomic2(SCM_SVC_BOOT, SCM_WDOG_DEBUG_BOOT_PART, 1, 0); else ret = scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_BOOT, SCM_WDOG_DEBUG_BOOT_PART), &desc); if (ret) pr_err("Failed to disable secure wdog debug: %d\n", ret); halt_spmi_pmic_arbiter(); deassert_ps_hold(); msleep(10000); } static void do_msm_poweroff(void) { /* TODO: Add poweroff capability */ do_msm_restart(&restart_nb, 0, NULL); int ret; struct scm_desc desc = { .args[0] = 1, .args[1] = 0, .arginfo = SCM_ARGS(2), }; pr_notice("Powering off the SoC\n"); #ifdef CONFIG_QCOM_DLOAD_MODE set_dload_mode(0); #endif qpnp_pon_system_pwr_off(PON_POWER_OFF_SHUTDOWN); /* Needed to bypass debug image on some chips */ if (!is_scm_armv8()) ret = scm_call_atomic2(SCM_SVC_BOOT, SCM_WDOG_DEBUG_BOOT_PART, 1, 0); else ret = scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_BOOT, SCM_WDOG_DEBUG_BOOT_PART), &desc); if (ret) pr_err("Failed to disable wdog debug: %d\n", ret); halt_spmi_pmic_arbiter(); deassert_ps_hold(); msleep(10000); pr_err("Powering off has failed\n"); } static int msm_restart_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct resource *mem; struct device_node *np; int ret = 0; #ifdef CONFIG_QCOM_DLOAD_MODE if (scm_is_call_available(SCM_SVC_BOOT, SCM_DLOAD_CMD) > 0) scm_dload_supported = true; atomic_notifier_chain_register(&panic_notifier_list, &panic_blk); np = of_find_compatible_node(NULL, NULL, DL_MODE_PROP); if (!np) { pr_err("unable to find DT imem DLOAD mode node\n"); } else { dload_mode_addr = of_iomap(np, 0); if (!dload_mode_addr) pr_err("unable to map imem DLOAD offset\n"); } np = of_find_compatible_node(NULL, NULL, EDL_MODE_PROP); if (!np) { pr_err("unable to find DT imem EDLOAD mode node\n"); } else { emergency_dload_mode_addr = of_iomap(np, 0); if (!emergency_dload_mode_addr) pr_err("unable to map imem EDLOAD mode offset\n"); } mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); #endif np = of_find_compatible_node(NULL, NULL, "qcom,msm-imem-restart_reason"); if (!np) { pr_err("unable to find DT imem restart reason node\n"); } else { restart_reason = of_iomap(np, 0); if (!restart_reason) { pr_err("unable to map imem restart reason offset\n"); ret = -ENOMEM; goto err_restart_reason; } } mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pshold-base"); msm_ps_hold = devm_ioremap_resource(dev, mem); if (IS_ERR(msm_ps_hold)) return PTR_ERR(msm_ps_hold); register_restart_handler(&restart_nb); mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tcsr-boot-misc-detect"); if (mem) tcsr_boot_misc_detect = mem->start; pm_power_off = do_msm_poweroff; arm_pm_restart = do_msm_restart; if (scm_is_call_available(SCM_SVC_PWR, SCM_IO_DISABLE_PMIC_ARBITER) > 0) scm_pmic_arbiter_disable_supported = true; if (scm_is_call_available(SCM_SVC_PWR, SCM_IO_DEASSERT_PS_HOLD) > 0) scm_deassert_ps_hold_supported = true; download_mode = scm_is_secure_device(); set_dload_mode(download_mode); return 0; err_restart_reason: #ifdef CONFIG_QCOM_DLOAD_MODE iounmap(emergency_dload_mode_addr); iounmap(dload_mode_addr); #endif return ret; } static const struct of_device_id of_msm_restart_match[] = { Loading include/soc/qcom/restart.h 0 → 100644 +24 −0 Original line number Diff line number Diff line /* Copyright (c) 2011-2014, 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. * */ #ifndef _ASM_ARCH_MSM_RESTART_H_ #define _ASM_ARCH_MSM_RESTART_H_ #define RESTART_NORMAL 0x0 #define RESTART_DLOAD 0x1 void msm_set_restart_mode(int mode); extern int pmic_reset_irq; #endif Loading
drivers/power/reset/Kconfig +19 −6 Original line number Diff line number Diff line Loading @@ -98,12 +98,6 @@ config POWER_RESET_IMX say N here or disable in dts to make sure pm_power_off never be overwrote wrongly by this driver. config POWER_RESET_MSM bool "Qualcomm MSM power-off driver" depends on ARCH_QCOM help Power off and restart support for Qualcomm boards. config POWER_RESET_LTC2952 bool "LTC2952 PowerPath power-off driver" depends on OF_GPIO Loading @@ -111,6 +105,25 @@ config POWER_RESET_LTC2952 This driver supports an external powerdown trigger and board power down via the LTC2952. Bindings are made in the device tree. config POWER_RESET_QCOM bool "Qualcomm Technologies, Inc. MSM power-off driver" depends on ARCH_MSM || ARCH_QCOM depends on POWER_RESET help Power off and restart support for Qualcomm Technologies, Inc. boards. This driver supports board power down and restart using PMIC PS_HOLD. Say Y here if you have a Qualcomm Tecnologies, Inc. board and wish to enable restart driver. config QCOM_DLOAD_MODE bool "Qualcomm Technologies, Inc. download mode" depends on POWER_RESET_QCOM help This makes the SoC enter download mode when it resets due to a kernel panic. Note that this doesn't by itself make the kernel reboot on a kernel panic - that must be enabled via another mechanism. config POWER_RESET_QNAP bool "QNAP power-off driver" depends on OF_GPIO && PLAT_ORION Loading
drivers/power/reset/Makefile +1 −1 Original line number Diff line number Diff line Loading @@ -9,7 +9,7 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o obj-$(CONFIG_POWER_RESET_IMX) += imx-snvs-poweroff.o obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o obj-$(CONFIG_POWER_RESET_QCOM) += msm-poweroff.o obj-$(CONFIG_POWER_RESET_LTC2952) += ltc2952-poweroff.o obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o Loading
drivers/power/reset/msm-poweroff.c +421 −13 Original line number Diff line number Diff line /* Copyright (c) 2013, The Linux Foundation. All rights reserved. /* Copyright (c) 2013-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 Loading @@ -15,49 +15,457 @@ #include <linux/err.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/io.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/module.h> #include <linux/reboot.h> #include <linux/pm.h> #include <linux/delay.h> #include <linux/qpnp/power-on.h> #include <linux/of_address.h> #include <asm/cacheflush.h> #include <asm/system_misc.h> #include <soc/qcom/scm.h> #include <soc/qcom/restart.h> #include <soc/qcom/watchdog.h> #define EMERGENCY_DLOAD_MAGIC1 0x322A4F99 #define EMERGENCY_DLOAD_MAGIC2 0xC67E4350 #define EMERGENCY_DLOAD_MAGIC3 0x77777777 #define SCM_IO_DISABLE_PMIC_ARBITER 1 #define SCM_IO_DEASSERT_PS_HOLD 2 #define SCM_WDOG_DEBUG_BOOT_PART 0x9 #define SCM_DLOAD_MODE 0X10 #define SCM_EDLOAD_MODE 0X01 #define SCM_DLOAD_CMD 0x10 static int restart_mode; void *restart_reason; static bool scm_pmic_arbiter_disable_supported; static bool scm_deassert_ps_hold_supported; /* Download mode master kill-switch */ static void __iomem *msm_ps_hold; static int do_msm_restart(struct notifier_block *nb, unsigned long action, void *data) { writel(0, msm_ps_hold); mdelay(10000); static phys_addr_t tcsr_boot_misc_detect; #ifdef CONFIG_QCOM_DLOAD_MODE #define EDL_MODE_PROP "qcom,msm-imem-emergency_download_mode" #define DL_MODE_PROP "qcom,msm-imem-download_mode" static int in_panic; static void *dload_mode_addr; static bool dload_mode_enabled; static void *emergency_dload_mode_addr; static bool scm_dload_supported; static int dload_set(const char *val, struct kernel_param *kp); static int download_mode = 1; module_param_call(download_mode, dload_set, param_get_int, &download_mode, 0644); static int panic_prep_restart(struct notifier_block *this, unsigned long event, void *ptr) { in_panic = 1; return NOTIFY_DONE; } static struct notifier_block restart_nb = { .notifier_call = do_msm_restart, .priority = 128, static struct notifier_block panic_blk = { .notifier_call = panic_prep_restart, }; int scm_set_dload_mode(int arg1, int arg2) { struct scm_desc desc = { .args[0] = arg1, .args[1] = arg2, .arginfo = SCM_ARGS(2), }; if (!scm_dload_supported) { if (tcsr_boot_misc_detect) return scm_io_write(tcsr_boot_misc_detect, arg1); return 0; } if (!is_scm_armv8()) return scm_call_atomic2(SCM_SVC_BOOT, SCM_DLOAD_CMD, arg1, arg2); return scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_BOOT, SCM_DLOAD_CMD), &desc); } static void set_dload_mode(int on) { int ret; if (dload_mode_addr) { __raw_writel(on ? 0xE47B337D : 0, dload_mode_addr); __raw_writel(on ? 0xCE14091A : 0, dload_mode_addr + sizeof(unsigned int)); /* Make sure the download cookie is updated */ mb(); } ret = scm_set_dload_mode(on ? SCM_DLOAD_MODE : 0, 0); if (ret) pr_err("Failed to set secure DLOAD mode: %d\n", ret); dload_mode_enabled = on; } static bool get_dload_mode(void) { return dload_mode_enabled; } static void enable_emergency_dload_mode(void) { int ret; if (emergency_dload_mode_addr) { __raw_writel(EMERGENCY_DLOAD_MAGIC1, emergency_dload_mode_addr); __raw_writel(EMERGENCY_DLOAD_MAGIC2, emergency_dload_mode_addr + sizeof(unsigned int)); __raw_writel(EMERGENCY_DLOAD_MAGIC3, emergency_dload_mode_addr + (2 * sizeof(unsigned int))); /* Need disable the pmic wdt, then the emergency dload mode * will not auto reset. */ qpnp_pon_wd_config(0); /* Make sure all the cookied are flushed to memory */ mb(); } ret = scm_set_dload_mode(SCM_EDLOAD_MODE, 0); if (ret) pr_err("Failed to set secure EDLOAD mode: %d\n", ret); } static int dload_set(const char *val, struct kernel_param *kp) { int ret; int old_val = download_mode; ret = param_set_int(val, kp); if (ret) return ret; /* If download_mode is not zero or one, ignore. */ if (download_mode >> 1) { download_mode = old_val; return -EINVAL; } set_dload_mode(download_mode); return 0; } #else #define set_dload_mode(x) do {} while (0) static void enable_emergency_dload_mode(void) { pr_err("dload mode is not enabled on target\n"); } static bool get_dload_mode(void) { return false; } #endif void msm_set_restart_mode(int mode) { restart_mode = mode; } EXPORT_SYMBOL(msm_set_restart_mode); /* * Force the SPMI PMIC arbiter to shutdown so that no more SPMI transactions * are sent from the MSM to the PMIC. This is required in order to avoid an * SPMI lockup on certain PMIC chips if PS_HOLD is lowered in the middle of * an SPMI transaction. */ static void halt_spmi_pmic_arbiter(void) { struct scm_desc desc = { .args[0] = 0, .arginfo = SCM_ARGS(1), }; if (scm_pmic_arbiter_disable_supported) { pr_crit("Calling SCM to disable SPMI PMIC arbiter\n"); if (!is_scm_armv8()) scm_call_atomic1(SCM_SVC_PWR, SCM_IO_DISABLE_PMIC_ARBITER, 0); else scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_PWR, SCM_IO_DISABLE_PMIC_ARBITER), &desc); } } static void msm_restart_prepare(const char *cmd) { #ifdef CONFIG_QCOM_DLOAD_MODE bool need_warm_reset = false; /* Write download mode flags if we're panic'ing * Write download mode flags if restart_mode says so * Kill download mode if master-kill switch is set */ set_dload_mode(download_mode && (in_panic || restart_mode == RESTART_DLOAD)); #endif if (qpnp_pon_check_hard_reset_stored()) { /* Set warm reset as true when device is in dload mode */ if (get_dload_mode() || ((cmd != NULL && cmd[0] != '\0') && !strcmp(cmd, "edl"))) need_warm_reset = true; } else { need_warm_reset = (get_dload_mode() || (cmd != NULL && cmd[0] != '\0')); } /* Hard reset the PMIC unless memory contents must be maintained. */ if (need_warm_reset) qpnp_pon_system_pwr_off(PON_POWER_OFF_WARM_RESET); else qpnp_pon_system_pwr_off(PON_POWER_OFF_HARD_RESET); if (cmd != NULL) { if (!strncmp(cmd, "bootloader", 10)) { qpnp_pon_set_restart_reason( PON_RESTART_REASON_BOOTLOADER); __raw_writel(0x77665500, restart_reason); } else if (!strncmp(cmd, "recovery", 8)) { qpnp_pon_set_restart_reason( PON_RESTART_REASON_RECOVERY); __raw_writel(0x77665502, restart_reason); } else if (!strcmp(cmd, "rtc")) { qpnp_pon_set_restart_reason( PON_RESTART_REASON_RTC); __raw_writel(0x77665503, restart_reason); } else if (!strcmp(cmd, "dm-verity device corrupted")) { qpnp_pon_set_restart_reason( PON_RESTART_REASON_DMVERITY_CORRUPTED); __raw_writel(0x77665508, restart_reason); } else if (!strcmp(cmd, "dm-verity enforcing")) { qpnp_pon_set_restart_reason( PON_RESTART_REASON_DMVERITY_ENFORCE); __raw_writel(0x77665509, restart_reason); } else if (!strcmp(cmd, "keys clear")) { qpnp_pon_set_restart_reason( PON_RESTART_REASON_KEYS_CLEAR); __raw_writel(0x7766550a, restart_reason); } else if (!strncmp(cmd, "oem-", 4)) { unsigned long code; int ret; ret = kstrtoul(cmd + 4, 16, &code); if (!ret) __raw_writel(0x6f656d00 | (code & 0xff), restart_reason); } else if (!strncmp(cmd, "edl", 3)) { enable_emergency_dload_mode(); } else { __raw_writel(0x77665501, restart_reason); } } flush_cache_all(); /*outer_flush_all is not supported by 64bit kernel*/ #ifndef CONFIG_ARM64 outer_flush_all(); #endif } /* * Deassert PS_HOLD to signal the PMIC that we are ready to power down or reset. * Do this by calling into the secure environment, if available, or by directly * writing to a hardware register. * * This function should never return. */ static void deassert_ps_hold(void) { struct scm_desc desc = { .args[0] = 0, .arginfo = SCM_ARGS(1), }; if (scm_deassert_ps_hold_supported) { /* This call will be available on ARMv8 only */ scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_PWR, SCM_IO_DEASSERT_PS_HOLD), &desc); } /* Fall-through to the direct write in case the scm_call "returns" */ __raw_writel(0, msm_ps_hold); } static void do_msm_restart(enum reboot_mode reboot_mode, const char *cmd) { int ret; struct scm_desc desc = { .args[0] = 1, .args[1] = 0, .arginfo = SCM_ARGS(2), }; pr_notice("Going down for restart now\n"); msm_restart_prepare(cmd); #ifdef CONFIG_QCOM_DLOAD_MODE /* * Trigger a watchdog bite here and if this fails, * device will take the usual restart path. */ if (WDOG_BITE_ON_PANIC && in_panic) msm_trigger_wdog_bite(); #endif /* Needed to bypass debug image on some chips */ if (!is_scm_armv8()) ret = scm_call_atomic2(SCM_SVC_BOOT, SCM_WDOG_DEBUG_BOOT_PART, 1, 0); else ret = scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_BOOT, SCM_WDOG_DEBUG_BOOT_PART), &desc); if (ret) pr_err("Failed to disable secure wdog debug: %d\n", ret); halt_spmi_pmic_arbiter(); deassert_ps_hold(); msleep(10000); } static void do_msm_poweroff(void) { /* TODO: Add poweroff capability */ do_msm_restart(&restart_nb, 0, NULL); int ret; struct scm_desc desc = { .args[0] = 1, .args[1] = 0, .arginfo = SCM_ARGS(2), }; pr_notice("Powering off the SoC\n"); #ifdef CONFIG_QCOM_DLOAD_MODE set_dload_mode(0); #endif qpnp_pon_system_pwr_off(PON_POWER_OFF_SHUTDOWN); /* Needed to bypass debug image on some chips */ if (!is_scm_armv8()) ret = scm_call_atomic2(SCM_SVC_BOOT, SCM_WDOG_DEBUG_BOOT_PART, 1, 0); else ret = scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_BOOT, SCM_WDOG_DEBUG_BOOT_PART), &desc); if (ret) pr_err("Failed to disable wdog debug: %d\n", ret); halt_spmi_pmic_arbiter(); deassert_ps_hold(); msleep(10000); pr_err("Powering off has failed\n"); } static int msm_restart_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct resource *mem; struct device_node *np; int ret = 0; #ifdef CONFIG_QCOM_DLOAD_MODE if (scm_is_call_available(SCM_SVC_BOOT, SCM_DLOAD_CMD) > 0) scm_dload_supported = true; atomic_notifier_chain_register(&panic_notifier_list, &panic_blk); np = of_find_compatible_node(NULL, NULL, DL_MODE_PROP); if (!np) { pr_err("unable to find DT imem DLOAD mode node\n"); } else { dload_mode_addr = of_iomap(np, 0); if (!dload_mode_addr) pr_err("unable to map imem DLOAD offset\n"); } np = of_find_compatible_node(NULL, NULL, EDL_MODE_PROP); if (!np) { pr_err("unable to find DT imem EDLOAD mode node\n"); } else { emergency_dload_mode_addr = of_iomap(np, 0); if (!emergency_dload_mode_addr) pr_err("unable to map imem EDLOAD mode offset\n"); } mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); #endif np = of_find_compatible_node(NULL, NULL, "qcom,msm-imem-restart_reason"); if (!np) { pr_err("unable to find DT imem restart reason node\n"); } else { restart_reason = of_iomap(np, 0); if (!restart_reason) { pr_err("unable to map imem restart reason offset\n"); ret = -ENOMEM; goto err_restart_reason; } } mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pshold-base"); msm_ps_hold = devm_ioremap_resource(dev, mem); if (IS_ERR(msm_ps_hold)) return PTR_ERR(msm_ps_hold); register_restart_handler(&restart_nb); mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tcsr-boot-misc-detect"); if (mem) tcsr_boot_misc_detect = mem->start; pm_power_off = do_msm_poweroff; arm_pm_restart = do_msm_restart; if (scm_is_call_available(SCM_SVC_PWR, SCM_IO_DISABLE_PMIC_ARBITER) > 0) scm_pmic_arbiter_disable_supported = true; if (scm_is_call_available(SCM_SVC_PWR, SCM_IO_DEASSERT_PS_HOLD) > 0) scm_deassert_ps_hold_supported = true; download_mode = scm_is_secure_device(); set_dload_mode(download_mode); return 0; err_restart_reason: #ifdef CONFIG_QCOM_DLOAD_MODE iounmap(emergency_dload_mode_addr); iounmap(dload_mode_addr); #endif return ret; } static const struct of_device_id of_msm_restart_match[] = { Loading
include/soc/qcom/restart.h 0 → 100644 +24 −0 Original line number Diff line number Diff line /* Copyright (c) 2011-2014, 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. * */ #ifndef _ASM_ARCH_MSM_RESTART_H_ #define _ASM_ARCH_MSM_RESTART_H_ #define RESTART_NORMAL 0x0 #define RESTART_DLOAD 0x1 void msm_set_restart_mode(int mode); extern int pmic_reset_irq; #endif