368 lines
9.8 KiB
C
368 lines
9.8 KiB
C
|
/*
|
||
|
* Copyright (c) 2015 Samsung Electronics Co., Ltd.
|
||
|
* http://www.samsung.com/
|
||
|
*
|
||
|
* Exynos - Support SoC specific Reboot
|
||
|
* Author: Hosung Kim <hosung0.kim@samsung.com>
|
||
|
*
|
||
|
* 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 <linux/io.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_gpio.h>
|
||
|
#include <linux/input.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/of_address.h>
|
||
|
#include <linux/of_platform.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/reboot.h>
|
||
|
#include <linux/soc/samsung/exynos-soc.h>
|
||
|
#include <linux/debug-snapshot.h>
|
||
|
|
||
|
#ifdef CONFIG_EXYNOS_ACPM
|
||
|
#include <soc/samsung/acpm_ipc_ctrl.h>
|
||
|
#endif
|
||
|
#include <soc/samsung/exynos-pmu.h>
|
||
|
|
||
|
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);
|