blob: da6e6649e4fe8c35d2982c8e7dafb42c6beb9c51 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/delay.h>
#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 <asm/system_misc.h>
#include <linux/amlogic/iomap.h>
#include <linux/amlogic/cpu_version.h>
#include <linux/amlogic/iomap.h>
#include <linux/amlogic/reboot.h>
//#include <asm/compiler.h>
#include <linux/kdebug.h>
#include <linux/arm-smccc.h>
#include <linux/syscore_ops.h>
#include <linux/cpu.h>
static void __iomem *reboot_reason_vaddr;
static u32 psci_function_id_restart;
static u32 psci_function_id_poweroff;
static char *kernel_panic;
static struct reboot_reason_str reboot_reason_name[] = {
[MESON_COLD_REBOOT] = { .name = "cold reboot" },
[MESON_NORMAL_BOOT] = { .name = "normal boot" },
[MESON_FACTORY_RESET_REBOOT] = { .name = "factory reset reboot" },
[MESON_UPDATE_REBOOT] = { .name = "update reboot" },
[MESON_FASTBOOT_REBOOT] = { .name = "fastboot reboot" },
[MESON_UBOOT_SUSPEND] = { .name = "uboot suspend" },
[MESON_HIBERNATE] = { .name = "hibernate" },
[MESON_BOOTLOADER_REBOOT] = { .name = "bootloader reboot" },
[MESON_RPMBP_REBOOT] = { .name = "rpmbp reboot" },
[MESON_QUIESCENT_REBOOT] = { .name = "quiescent reboot" },
[MESON_RESCUEPARTY_REBOOT] = { .name = "rescueparty reboot" },
[MESON_KERNEL_PANIC] = { .name = "kernel panic" },
[MESON_RECOVERY_QUIESCENT_REBOOT] = {
.name = "recovery quiescent reboot" },
[MESON_FFV_REBOOT] = { .name = "ffv reboot" },
};
u32 get_reboot_reason(void)
{
u32 value;
u32 reboot_reason;
value = readl(reboot_reason_vaddr);
reboot_reason = (value >> 12) & 0xf;
return reboot_reason;
}
EXPORT_SYMBOL_GPL(get_reboot_reason);
static u32 parse_reason(const char *cmd)
{
u32 reboot_reason = MESON_NORMAL_BOOT;
if (cmd) {
if (strcmp(cmd, "recovery") == 0 ||
strcmp(cmd, "factory_reset") == 0)
reboot_reason = MESON_FACTORY_RESET_REBOOT;
else if (strcmp(cmd, "cold_boot") == 0)
reboot_reason = MESON_COLD_REBOOT;
else if (strcmp(cmd, "update") == 0)
reboot_reason = MESON_UPDATE_REBOOT;
else if (strcmp(cmd, "fastboot") == 0)
reboot_reason = MESON_FASTBOOT_REBOOT;
else if (strcmp(cmd, "bootloader") == 0)
reboot_reason = MESON_BOOTLOADER_REBOOT;
else if (strcmp(cmd, "rpmbp") == 0)
reboot_reason = MESON_RPMBP_REBOOT;
else if (strcmp(cmd, "rescueparty") == 0)
reboot_reason = MESON_RESCUEPARTY_REBOOT;
else if (strcmp(cmd, "uboot_suspend") == 0)
reboot_reason = MESON_UBOOT_SUSPEND;
else if (strcmp(cmd, "quiescent") == 0 ||
strcmp(cmd, "userrequested,recovery,quiescent") == 0 ||
strcmp(cmd, "reboot-ab-update,quiescent") == 0 ||
strcmp(cmd, "unattended,ota_update,quiescent") == 0 ||
strcmp(cmd, ",quiescent") == 0)
reboot_reason = MESON_QUIESCENT_REBOOT;
else if (strcmp(cmd, "recovery,quiescent") == 0 ||
strcmp(cmd, "factory_reset,quiescent") == 0 ||
strcmp(cmd, "quiescent,recovery") == 0 ||
strcmp(cmd, "quiescent,factory_reset") == 0)
reboot_reason = MESON_RECOVERY_QUIESCENT_REBOOT;
else if (strcmp(cmd, "ffv_reboot") == 0)
reboot_reason = MESON_FFV_REBOOT;
} else {
if (kernel_panic) {
if (strcmp(kernel_panic, "kernel_panic") == 0)
reboot_reason = MESON_KERNEL_PANIC;
}
}
pr_info("reboot reason %d\n", reboot_reason);
return reboot_reason;
}
static noinline int __invoke_psci_fn_smc(u64 function_id, u64 arg0, u64 arg1,
u64 arg2)
{
struct arm_smccc_res res;
arm_smccc_smc((unsigned long)function_id,
(unsigned long)arg0,
(unsigned long)arg1,
(unsigned long)arg2,
0, 0, 0, 0, &res);
return res.a0;
}
void meson_smc_restart(u64 function_id, u64 reboot_reason)
{
__invoke_psci_fn_smc(function_id,
reboot_reason, 0, 0);
}
void meson_common_restart(char mode, const char *cmd)
{
u32 reboot_reason = parse_reason(cmd);
if (psci_function_id_restart)
meson_smc_restart((u64)psci_function_id_restart,
(u64)reboot_reason);
}
static int do_aml_restart(struct notifier_block *nb, unsigned long reboot_mode,
void *cmd)
{
meson_common_restart(reboot_mode, cmd);
return NOTIFY_DONE;
}
static void do_aml_poweroff(void)
{
/* TODO: Add poweroff capability */
__invoke_psci_fn_smc(0x82000042, 1, 0, 0);
__invoke_psci_fn_smc(psci_function_id_poweroff,
0, 0, 0);
}
static int panic_notify(struct notifier_block *self,
unsigned long cmd, void *ptr)
{
kernel_panic = "kernel_panic";
return NOTIFY_DONE;
}
static struct notifier_block panic_notifier = {
.notifier_call = panic_notify,
};
ssize_t reboot_reason_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
unsigned int value, len, reboot_reason;
if (!reboot_reason_vaddr)
return 0;
value = readl(reboot_reason_vaddr);
reboot_reason = ((value >> 12) & 0xf);
len = sprintf(buf, "reboot reason = %d, %s\n", reboot_reason,
reboot_reason_name[reboot_reason].name);
return len;
}
static DEVICE_ATTR_RO(reboot_reason);
static void disable_non_bootcpu_shutdown(void)
{
int error = 0, cpu, primary = 0;
if (!cpu_online(primary))
primary = cpumask_first(cpu_online_mask);
cpu_hotplug_enable();
for_each_online_cpu(cpu) {
if (cpu == primary)
continue;
error = cpu_down(cpu);
if (error) {
pr_err("Error taking CPU%d down: %d\n", cpu, error);
break;
}
}
if (!error)
WARN_ON(num_online_cpus() > 1);
else
pr_err("Non-boot CPUs are not disabled\n");
cpu_hotplug_disable();
}
static struct syscore_ops disable_non_bootcpu_syscore_ops = {
.shutdown = disable_non_bootcpu_shutdown,
};
static int __init reboot_pm_init_ops(void)
{
register_syscore_ops(&disable_non_bootcpu_syscore_ops);
return 0;
}
static struct notifier_block aml_restart_nb = {
.notifier_call = do_aml_restart,
.priority = 130,
};
static int aml_restart_probe(struct platform_device *pdev)
{
u32 id;
int ret;
if (!of_property_read_u32(pdev->dev.of_node, "sys_reset", &id)) {
psci_function_id_restart = id;
register_restart_handler(&aml_restart_nb);
}
if (!of_property_read_u32(pdev->dev.of_node, "sys_poweroff", &id)) {
psci_function_id_poweroff = id;
pm_power_off = do_aml_poweroff;
}
reboot_reason_vaddr = devm_platform_ioremap_resource(pdev, 0);
if (!IS_ERR(reboot_reason_vaddr)) {
ret = device_create_file(&pdev->dev, &dev_attr_reboot_reason);
if (ret != 0)
pr_err("%s, device create file failed, ret = %d!\n",
__func__, ret);
}
if (of_property_read_bool(pdev->dev.of_node,
"dis_nb_cpus_in_shutdown")) {
pr_info("Enable disable_nonboot_cpus in syscore shutdown.\n");
reboot_pm_init_ops();
}
ret = register_die_notifier(&panic_notifier);
if (ret != 0) {
pr_err("%s,register die notifier failed,ret =%d!\n",
__func__, ret);
return ret;
}
/* Register a call for panic conditions. */
ret = atomic_notifier_chain_register(&panic_notifier_list,
&panic_notifier);
if (ret != 0) {
pr_err("%s,register panic notifier failed,ret =%d!\n",
__func__, ret);
return ret;
}
return 0;
}
static const struct of_device_id of_aml_restart_match[] = {
{ .compatible = "aml, reboot", },
{},
};
MODULE_DEVICE_TABLE(of, of_aml_restart_match);
static struct platform_driver aml_restart_driver = {
.probe = aml_restart_probe,
.driver = {
.name = "aml-restart",
.of_match_table = of_match_ptr(of_aml_restart_match),
},
};
module_platform_driver(aml_restart_driver);
MODULE_LICENSE("GPL");