// SPDX-License-Identifier: GPL-2.0-only /* * exynos-reboot.c - Samsung Exynos SoC reset code * * Copyright (c) 2019-2020 Samsung Electronics Co., Ltd. * * Author: Hyunki Koo */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define SPMI_VGPIO_RX_0 0 #define SPMI_ONOB_BIT 9 #define I3C_HCI_VGPIO_RX_MON 0x2414 #define I3C_HCI_ONOB_BIT 15 static struct regmap *pmureg; static struct regmap *spmireg; static struct regmap *i3c_hci_reg; struct exynos_reboot_helper_ops exynos_reboot_ops; EXPORT_SYMBOL(exynos_reboot_ops); static u32 reboot_offset, reboot_trigger; static u32 reboot_cmd_offset; static u32 shutdown_offset, shutdown_trigger; extern void exynos_acpm_reboot(void); void exynos_reboot_register_acpm_ops(void *acpm_reboot_func) { if (acpm_reboot_func) exynos_reboot_ops.acpm_reboot = acpm_reboot_func; pr_info("Exynos Reboot: Add %s funtions\n", acpm_reboot_func ? "acpm_reboot" : "NULL"); } EXPORT_SYMBOL(exynos_reboot_register_acpm_ops); void exynos_reboot_register_pmic_ops(void *main_wa, void *sub_wa, void *seq_wa, void *pwrkey) { if (main_wa) exynos_reboot_ops.pmic_off_main_wa = main_wa; if (sub_wa) exynos_reboot_ops.pmic_off_sub_wa = sub_wa; if (seq_wa) exynos_reboot_ops.pmic_off_seq_wa = seq_wa; if (pwrkey) exynos_reboot_ops.pmic_pwrkey_status = pwrkey; pr_info("Exynos Reboot: Add %s%s%s%s funtions\n", main_wa ? "main_wa, " : "", sub_wa ? "sub_wa, " : "", seq_wa ? "seq_wa, " : "", pwrkey ? "pwrkey" : ""); } EXPORT_SYMBOL(exynos_reboot_register_pmic_ops); void exynos_reboot_print_socinfo(void) { u32 soc_id, revision; /* Check by each SoC */ soc_id = exynos_soc_info.product_id; revision = exynos_soc_info.revision; pr_info("Exynos Reboot: SOC ID %X. Revision: %x\n", soc_id, revision); } EXPORT_SYMBOL(exynos_reboot_print_socinfo); int exynos_reboot_pwrkey_status(void) { u32 val; if (exynos_reboot_ops.pmic_pwrkey_status) return exynos_reboot_ops.pmic_pwrkey_status(); /* Return 0: Release 1: Press */ if (!IS_ERR(spmireg)) { /* VGI_RX_0[9] 0: Release 1: Press */ regmap_read(spmireg, SPMI_VGPIO_RX_0, &val); pr_info("Exynos Reboot: SPMI VGI_RX_0 0x%08x\n", val); return (val >> SPMI_ONOB_BIT) & 0x1; } else if (!IS_ERR(i3c_hci_reg)) { /* VGPIO_RX_MON[15] 1: Release 0: Press */ regmap_read(i3c_hci_reg, I3C_HCI_VGPIO_RX_MON, &val); pr_info("Exynos Reboot: I3C_HCI_VGPIO_RX_MON 0x%08x\n", val); return !((val >> I3C_HCI_ONOB_BIT) & 0x1); }; pr_err("Exynos reboot, No pwrkey func. enforce release state\n"); return 0; } EXPORT_SYMBOL(exynos_reboot_pwrkey_status); #if !IS_ENABLED(CONFIG_SEC_REBOOT) static void exynos_power_off(void) { u32 poweroff_try = 0; /* PMIC EVT0 : power-off issue */ /* PWREN_MIF 0 after setting regulator controlled by PWREN_MIF to always-on */ if (exynos_reboot_ops.pmic_off_sub_wa) { if (exynos_reboot_ops.pmic_off_sub_wa() < 0) pr_err("pmic_off_sub_wa error\n"); } /* PMIC EVT1: Fix off-sequence */ if (exynos_reboot_ops.pmic_off_seq_wa) { if (exynos_reboot_ops.pmic_off_seq_wa() < 0) pr_err("pmic_off_seq_wa error\n"); } /* PMIC stuck bug fix */ if (exynos_reboot_ops.pmic_off_main_wa) { if (exynos_reboot_ops.pmic_off_main_wa() < 0) pr_err("pmic_off_main_wa error\n"); } exynos_reboot_print_socinfo(); pr_info("Exynos reboot, PWR Key(%d)\n", exynos_reboot_pwrkey_status()); while (1) { /* wait for power button release. * but after exynos_acpm_reboot is called * power on status cannot be read */ if ((poweroff_try) || (!exynos_reboot_pwrkey_status())) { if (exynos_reboot_ops.acpm_reboot) exynos_reboot_ops.acpm_reboot(); else pr_err("Exynos reboot, acpm_reboot not registered\n"); pr_emerg("Set PS_HOLD Low.\n"); regmap_update_bits(pmureg, shutdown_offset, shutdown_trigger, 0); ++poweroff_try; pr_emerg("Should not reach here! (poweroff_try:%d)\n", poweroff_try); } else { pr_info("PWR Key is not released (%d)\n", exynos_reboot_pwrkey_status()); } mdelay(1000); } } #endif #define EXYNOS_PMU_SYSIP_DAT0 (0x0810) #define REBOOT_MODE_NORMAL (0x00) #define REBOOT_MODE_FASTBOOTD (0x05) #define REBOOT_MODE_CHARGE (0x04) #define REBOOT_MODE_FASTBOOT (0x03) #define REBOOT_MODE_FACTORY (0x02) #define REBOOT_MODE_RECOVERY (0x01) static void exynos_reboot_parse(void *cmd) { if (cmd) { pr_info("Reboot command: (%s)\n", (char *)cmd); if (!strcmp((char *)cmd, "charge")) { regmap_write(pmureg, reboot_cmd_offset, REBOOT_MODE_CHARGE); } else if (!strcmp((char *)cmd, "bootloader") || !strcmp((char *)cmd, "fastboot") || !strcmp((char *)cmd, "bl") || !strcmp((char *)cmd, "fb")) { regmap_write(pmureg, reboot_cmd_offset, REBOOT_MODE_FASTBOOT); } else if (!strcmp((char *)cmd, "recovery")) { regmap_write(pmureg, reboot_cmd_offset, REBOOT_MODE_RECOVERY); } else if (!strcmp((char *)cmd, "sfactory")) { regmap_write(pmureg, reboot_cmd_offset, REBOOT_MODE_FACTORY); } } } static int exynos_restart_handler(struct notifier_block *this, unsigned long mode, void *cmd) { if (exynos_reboot_ops.acpm_reboot) exynos_reboot_ops.acpm_reboot(); else pr_err("Exynos reboot, acpm_reboot not registered\n"); exynos_reboot_parse(cmd); exynos_reboot_print_socinfo(); /* Do S/W Reset */ pr_emerg("%s: Exynos SoC reset right now\n", __func__); regmap_write(pmureg, reboot_offset, reboot_trigger); while (1) wfi(); return NOTIFY_DONE; } void exynos_mach_restart(const char *cmd) { exynos_restart_handler(NULL, 0, (void *)cmd); } EXPORT_SYMBOL_GPL(exynos_mach_restart); static struct notifier_block exynos_restart_nb = { .notifier_call = exynos_restart_handler, .priority = 128, }; static int exynos_reboot_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = pdev->dev.of_node; int err; pmureg = syscon_regmap_lookup_by_phandle(dev->of_node, "samsung,syscon-phandle"); if (IS_ERR(pmureg)) { pr_err("Fail to get regmap of PMU\n"); return PTR_ERR(pmureg); } spmireg = syscon_regmap_lookup_by_phandle(dev->of_node, "samsung,spmi-syscon-phandle"); i3c_hci_reg = syscon_regmap_lookup_by_phandle(dev->of_node, "samsung,i3c-hci-syscon-phandle"); if (IS_ERR(spmireg) && IS_ERR(i3c_hci_reg)) pr_warn("Fail to get regmap of SPMI or I3C_HCI\n"); if (of_property_read_u32(np, "reboot-offset", &reboot_offset) < 0) { pr_err("failed to find reboot-offset property\n"); return -EINVAL; } if (of_property_read_u32(np, "reboot-trigger", &reboot_trigger) < 0) { pr_err("failed to find reboot-trigger property\n"); return -EINVAL; } if (of_property_read_u32(np, "shutdown-offset", &shutdown_offset) < 0) { pr_err("failed to find shutdown-offset property\n"); return -EINVAL; } if (of_property_read_u32(np, "shutdown-trigger", &shutdown_trigger) < 0) { pr_err("failed to find shutdown-trigger property\n"); return -EINVAL; } if (of_property_read_u32(np, "reboot-cmd-offset", &reboot_cmd_offset) < 0) { pr_info("failed to find reboot-offset property, using default\n"); reboot_cmd_offset = EXYNOS_PMU_SYSIP_DAT0; } err = register_restart_handler(&exynos_restart_nb); if (err) { dev_err(&pdev->dev, "cannot register restart handler (err=%d)\n", err); } #if !IS_ENABLED(CONFIG_SEC_REBOOT) pm_power_off = exynos_power_off; #endif dev_info(&pdev->dev, "register restart handler successfully\n"); return err; } static const struct of_device_id exynos_reboot_of_match[] = { { .compatible = "samsung,exynos-reboot" }, {} }; static struct platform_driver exynos_reboot_driver = { .probe = exynos_reboot_probe, .driver = { .name = "exynos-reboot", .of_match_table = exynos_reboot_of_match, }, }; module_platform_driver(exynos_reboot_driver); MODULE_SOFTDEP("pre: exynos-chipid_v2"); MODULE_DESCRIPTION("Exynos Reboot driver"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:exynos-reboot");