16fbde6b4SJiaxun Yang // SPDX-License-Identifier: GPL-2.0-or-later
26fbde6b4SJiaxun Yang /*
36fbde6b4SJiaxun Yang *
46fbde6b4SJiaxun Yang * Copyright (C) 2007 Lemote, Inc. & Institute of Computing Technology
56fbde6b4SJiaxun Yang * Author: Fuxin Zhang, zhangfx@lemote.com
66fbde6b4SJiaxun Yang * Copyright (C) 2009 Lemote, Inc.
76fbde6b4SJiaxun Yang * Author: Zhangjin Wu, wuzhangjin@gmail.com
86fbde6b4SJiaxun Yang */
96ce48897SHuacai Chen #include <linux/cpu.h>
106ce48897SHuacai Chen #include <linux/delay.h>
116fbde6b4SJiaxun Yang #include <linux/init.h>
126ce48897SHuacai Chen #include <linux/kexec.h>
136fbde6b4SJiaxun Yang #include <linux/pm.h>
14*6c695c3cSJiaxun Yang #include <linux/reboot.h>
156ce48897SHuacai Chen #include <linux/slab.h>
166fbde6b4SJiaxun Yang
176ce48897SHuacai Chen #include <asm/bootinfo.h>
186fbde6b4SJiaxun Yang #include <asm/idle.h>
196fbde6b4SJiaxun Yang #include <asm/reboot.h>
20fa706927SLiao Chang #include <asm/bug.h>
216fbde6b4SJiaxun Yang
226fbde6b4SJiaxun Yang #include <loongson.h>
236fbde6b4SJiaxun Yang #include <boot_param.h>
246fbde6b4SJiaxun Yang
firmware_restart(struct sys_off_data * unusedd)25*6c695c3cSJiaxun Yang static int firmware_restart(struct sys_off_data *unusedd)
266fbde6b4SJiaxun Yang {
276fbde6b4SJiaxun Yang
286fbde6b4SJiaxun Yang void (*fw_restart)(void) = (void *)loongson_sysconf.restart_addr;
296fbde6b4SJiaxun Yang
306fbde6b4SJiaxun Yang fw_restart();
31*6c695c3cSJiaxun Yang return NOTIFY_DONE;
326fbde6b4SJiaxun Yang }
336fbde6b4SJiaxun Yang
firmware_poweroff(struct sys_off_data * unused)34*6c695c3cSJiaxun Yang static int firmware_poweroff(struct sys_off_data *unused)
356fbde6b4SJiaxun Yang {
366fbde6b4SJiaxun Yang void (*fw_poweroff)(void) = (void *)loongson_sysconf.poweroff_addr;
376fbde6b4SJiaxun Yang
386fbde6b4SJiaxun Yang fw_poweroff();
39*6c695c3cSJiaxun Yang return NOTIFY_DONE;
406fbde6b4SJiaxun Yang }
416fbde6b4SJiaxun Yang
426ce48897SHuacai Chen #ifdef CONFIG_KEXEC
436ce48897SHuacai Chen
446ce48897SHuacai Chen /* 0X80000000~0X80200000 is safe */
456ce48897SHuacai Chen #define MAX_ARGS 64
466ce48897SHuacai Chen #define KEXEC_CTRL_CODE 0xFFFFFFFF80100000UL
476ce48897SHuacai Chen #define KEXEC_ARGV_ADDR 0xFFFFFFFF80108000UL
486ce48897SHuacai Chen #define KEXEC_ARGV_SIZE COMMAND_LINE_SIZE
496ce48897SHuacai Chen #define KEXEC_ENVP_SIZE 4800
506ce48897SHuacai Chen
516ce48897SHuacai Chen static int kexec_argc;
526ce48897SHuacai Chen static int kdump_argc;
536ce48897SHuacai Chen static void *kexec_argv;
546ce48897SHuacai Chen static void *kdump_argv;
556ce48897SHuacai Chen static void *kexec_envp;
566ce48897SHuacai Chen
loongson_kexec_prepare(struct kimage * image)576ce48897SHuacai Chen static int loongson_kexec_prepare(struct kimage *image)
586ce48897SHuacai Chen {
596ce48897SHuacai Chen int i, argc = 0;
606ce48897SHuacai Chen unsigned int *argv;
616ce48897SHuacai Chen char *str, *ptr, *bootloader = "kexec";
626ce48897SHuacai Chen
636ce48897SHuacai Chen /* argv at offset 0, argv[] at offset KEXEC_ARGV_SIZE/2 */
646ce48897SHuacai Chen if (image->type == KEXEC_TYPE_DEFAULT)
656ce48897SHuacai Chen argv = (unsigned int *)kexec_argv;
666ce48897SHuacai Chen else
676ce48897SHuacai Chen argv = (unsigned int *)kdump_argv;
686ce48897SHuacai Chen
696ce48897SHuacai Chen argv[argc++] = (unsigned int)(KEXEC_ARGV_ADDR + KEXEC_ARGV_SIZE/2);
706ce48897SHuacai Chen
716ce48897SHuacai Chen for (i = 0; i < image->nr_segments; i++) {
726ce48897SHuacai Chen if (!strncmp(bootloader, (char *)image->segment[i].buf,
736ce48897SHuacai Chen strlen(bootloader))) {
746ce48897SHuacai Chen /*
756ce48897SHuacai Chen * convert command line string to array
766ce48897SHuacai Chen * of parameters (as bootloader does).
776ce48897SHuacai Chen */
786ce48897SHuacai Chen int offt;
796ce48897SHuacai Chen str = (char *)argv + KEXEC_ARGV_SIZE/2;
806ce48897SHuacai Chen memcpy(str, image->segment[i].buf, KEXEC_ARGV_SIZE/2);
816ce48897SHuacai Chen ptr = strchr(str, ' ');
826ce48897SHuacai Chen
836ce48897SHuacai Chen while (ptr && (argc < MAX_ARGS)) {
846ce48897SHuacai Chen *ptr = '\0';
856ce48897SHuacai Chen if (ptr[1] != ' ') {
866ce48897SHuacai Chen offt = (int)(ptr - str + 1);
876ce48897SHuacai Chen argv[argc] = KEXEC_ARGV_ADDR + KEXEC_ARGV_SIZE/2 + offt;
886ce48897SHuacai Chen argc++;
896ce48897SHuacai Chen }
906ce48897SHuacai Chen ptr = strchr(ptr + 1, ' ');
916ce48897SHuacai Chen }
926ce48897SHuacai Chen break;
936ce48897SHuacai Chen }
946ce48897SHuacai Chen }
956ce48897SHuacai Chen
966ce48897SHuacai Chen if (image->type == KEXEC_TYPE_DEFAULT)
976ce48897SHuacai Chen kexec_argc = argc;
986ce48897SHuacai Chen else
996ce48897SHuacai Chen kdump_argc = argc;
1006ce48897SHuacai Chen
1016ce48897SHuacai Chen /* kexec/kdump need a safe page to save reboot_code_buffer */
1026ce48897SHuacai Chen image->control_code_page = virt_to_page((void *)KEXEC_CTRL_CODE);
1036ce48897SHuacai Chen
1046ce48897SHuacai Chen return 0;
1056ce48897SHuacai Chen }
1066ce48897SHuacai Chen
loongson_kexec_shutdown(void)1076ce48897SHuacai Chen static void loongson_kexec_shutdown(void)
1086ce48897SHuacai Chen {
1096ce48897SHuacai Chen #ifdef CONFIG_SMP
1106ce48897SHuacai Chen int cpu;
1116ce48897SHuacai Chen
1126ce48897SHuacai Chen /* All CPUs go to reboot_code_buffer */
1136ce48897SHuacai Chen for_each_possible_cpu(cpu)
1146ce48897SHuacai Chen if (!cpu_online(cpu))
1156ce48897SHuacai Chen cpu_device_up(get_cpu_device(cpu));
1166a73022eSYouling Tang
1176a73022eSYouling Tang secondary_kexec_args[0] = TO_UNCAC(0x3ff01000);
1186ce48897SHuacai Chen #endif
1196ce48897SHuacai Chen kexec_args[0] = kexec_argc;
1206ce48897SHuacai Chen kexec_args[1] = fw_arg1;
1216ce48897SHuacai Chen kexec_args[2] = fw_arg2;
1226ce48897SHuacai Chen memcpy((void *)fw_arg1, kexec_argv, KEXEC_ARGV_SIZE);
1236ce48897SHuacai Chen memcpy((void *)fw_arg2, kexec_envp, KEXEC_ENVP_SIZE);
1246ce48897SHuacai Chen }
1256ce48897SHuacai Chen
loongson_crash_shutdown(struct pt_regs * regs)1266ce48897SHuacai Chen static void loongson_crash_shutdown(struct pt_regs *regs)
1276ce48897SHuacai Chen {
1286ce48897SHuacai Chen default_machine_crash_shutdown(regs);
1296ce48897SHuacai Chen kexec_args[0] = kdump_argc;
1306ce48897SHuacai Chen kexec_args[1] = fw_arg1;
1316ce48897SHuacai Chen kexec_args[2] = fw_arg2;
1326a73022eSYouling Tang #ifdef CONFIG_SMP
1336ce48897SHuacai Chen secondary_kexec_args[0] = TO_UNCAC(0x3ff01000);
1346a73022eSYouling Tang #endif
1356ce48897SHuacai Chen memcpy((void *)fw_arg1, kdump_argv, KEXEC_ARGV_SIZE);
1366ce48897SHuacai Chen memcpy((void *)fw_arg2, kexec_envp, KEXEC_ENVP_SIZE);
1376ce48897SHuacai Chen }
1386ce48897SHuacai Chen
1396ce48897SHuacai Chen #endif
1406ce48897SHuacai Chen
mips_reboot_setup(void)1416fbde6b4SJiaxun Yang static int __init mips_reboot_setup(void)
1426fbde6b4SJiaxun Yang {
143*6c695c3cSJiaxun Yang if (loongson_sysconf.restart_addr) {
144*6c695c3cSJiaxun Yang register_sys_off_handler(SYS_OFF_MODE_RESTART,
145*6c695c3cSJiaxun Yang SYS_OFF_PRIO_FIRMWARE,
146*6c695c3cSJiaxun Yang firmware_restart, NULL);
147*6c695c3cSJiaxun Yang }
148*6c695c3cSJiaxun Yang
149*6c695c3cSJiaxun Yang if (loongson_sysconf.poweroff_addr) {
150*6c695c3cSJiaxun Yang register_sys_off_handler(SYS_OFF_MODE_POWER_OFF,
151*6c695c3cSJiaxun Yang SYS_OFF_PRIO_FIRMWARE,
152*6c695c3cSJiaxun Yang firmware_poweroff, NULL);
153*6c695c3cSJiaxun Yang }
1546fbde6b4SJiaxun Yang
1556ce48897SHuacai Chen #ifdef CONFIG_KEXEC
1566ce48897SHuacai Chen kexec_argv = kmalloc(KEXEC_ARGV_SIZE, GFP_KERNEL);
157fa706927SLiao Chang if (WARN_ON(!kexec_argv))
158fa706927SLiao Chang return -ENOMEM;
159fa706927SLiao Chang
1606ce48897SHuacai Chen kdump_argv = kmalloc(KEXEC_ARGV_SIZE, GFP_KERNEL);
161fa706927SLiao Chang if (WARN_ON(!kdump_argv))
162fa706927SLiao Chang return -ENOMEM;
163fa706927SLiao Chang
1646ce48897SHuacai Chen kexec_envp = kmalloc(KEXEC_ENVP_SIZE, GFP_KERNEL);
165fa706927SLiao Chang if (WARN_ON(!kexec_envp))
166fa706927SLiao Chang return -ENOMEM;
167fa706927SLiao Chang
1686ce48897SHuacai Chen fw_arg1 = KEXEC_ARGV_ADDR;
1696ce48897SHuacai Chen memcpy(kexec_envp, (void *)fw_arg2, KEXEC_ENVP_SIZE);
1706ce48897SHuacai Chen
1716ce48897SHuacai Chen _machine_kexec_prepare = loongson_kexec_prepare;
1726ce48897SHuacai Chen _machine_kexec_shutdown = loongson_kexec_shutdown;
1736ce48897SHuacai Chen _machine_crash_shutdown = loongson_crash_shutdown;
1746ce48897SHuacai Chen #endif
1756ce48897SHuacai Chen
1766fbde6b4SJiaxun Yang return 0;
1776fbde6b4SJiaxun Yang }
1786fbde6b4SJiaxun Yang
1796fbde6b4SJiaxun Yang arch_initcall(mips_reboot_setup);
180