blob: bae9ec58f2924251d156a4e773915fe39ecb5b6e [file] [log] [blame]
/*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2023 Ventana Micro Systems Inc.
*
* Authors:
* Anup Patel <apatel@ventanamicro.com>
*/
#include <libfdt.h>
#include <sbi/sbi_ecall_interface.h>
#include <sbi/sbi_error.h>
#include <sbi/sbi_hart.h>
#include <sbi/sbi_system.h>
#include <sbi_utils/fdt/fdt_helper.h>
#include <sbi_utils/regmap/fdt_regmap.h>
#include <sbi_utils/reset/fdt_reset.h>
struct syscon_reset {
struct regmap *rmap;
u32 priority;
u32 offset;
u32 value;
u32 mask;
};
static struct syscon_reset poweroff;
static struct syscon_reset reboot;
static struct syscon_reset *syscon_reset_get(bool is_reboot, u32 type)
{
struct syscon_reset *reset = NULL;
switch (type) {
case SBI_SRST_RESET_TYPE_SHUTDOWN:
if (!is_reboot)
reset = &poweroff;
break;
case SBI_SRST_RESET_TYPE_COLD_REBOOT:
case SBI_SRST_RESET_TYPE_WARM_REBOOT:
if (is_reboot)
reset = &reboot;
break;
}
if (reset && !reset->rmap)
reset = NULL;
return reset;
}
static void syscon_reset_exec(struct syscon_reset *reset)
{
/* Issue the reset through regmap */
if (reset)
regmap_update_bits(reset->rmap, reset->offset,
reset->mask, reset->value);
/* hang !!! */
sbi_hart_hang();
}
static int syscon_poweroff_check(u32 type, u32 reason)
{
struct syscon_reset *reset = syscon_reset_get(false, type);
return (reset) ? reset->priority : 0;
}
static void syscon_do_poweroff(u32 type, u32 reason)
{
syscon_reset_exec(syscon_reset_get(false, type));
}
static struct sbi_system_reset_device syscon_poweroff = {
.name = "syscon-poweroff",
.system_reset_check = syscon_poweroff_check,
.system_reset = syscon_do_poweroff
};
static int syscon_reboot_check(u32 type, u32 reason)
{
struct syscon_reset *reset = syscon_reset_get(true, type);
return (reset) ? reset->priority : 0;
}
static void syscon_do_reboot(u32 type, u32 reason)
{
syscon_reset_exec(syscon_reset_get(true, type));
}
static struct sbi_system_reset_device syscon_reboot = {
.name = "syscon-reboot",
.system_reset_check = syscon_reboot_check,
.system_reset = syscon_do_reboot
};
static int syscon_reset_init(void *fdt, int nodeoff,
const struct fdt_match *match)
{
int rc, len;
const fdt32_t *val, *mask;
bool is_reboot = (ulong)match->data;
struct syscon_reset *reset = (is_reboot) ? &reboot : &poweroff;
if (reset->rmap)
return SBI_EALREADY;
rc = fdt_regmap_get(fdt, nodeoff, &reset->rmap);
if (rc)
return rc;
val = fdt_getprop(fdt, nodeoff, "priority", &len);
reset->priority = (val && len > 0) ? fdt32_to_cpu(*val) : 192;
val = fdt_getprop(fdt, nodeoff, "offset", &len);
if (val && len > 0)
reset->offset = fdt32_to_cpu(*val);
else
return SBI_EINVAL;
val = fdt_getprop(fdt, nodeoff, "value", &len);
mask = fdt_getprop(fdt, nodeoff, "mask", &len);
if (!val && !mask)
return SBI_EINVAL;
if (!val) {
/* support old binding */
reset->value = fdt32_to_cpu(*mask);
reset->mask = 0xFFFFFFFF;
} else if (!mask) {
/* support value without mask */
reset->value = fdt32_to_cpu(*val);
reset->mask = 0xFFFFFFFF;
} else {
reset->value = fdt32_to_cpu(*val);
reset->mask = fdt32_to_cpu(*mask);
}
if (is_reboot)
sbi_system_reset_add_device(&syscon_reboot);
else
sbi_system_reset_add_device(&syscon_poweroff);
return 0;
}
static const struct fdt_match syscon_poweroff_match[] = {
{ .compatible = "syscon-poweroff", .data = (const void *)false },
{ },
};
struct fdt_reset fdt_syscon_poweroff = {
.match_table = syscon_poweroff_match,
.init = syscon_reset_init,
};
static const struct fdt_match syscon_reboot_match[] = {
{ .compatible = "syscon-reboot", .data = (const void *)true },
{ },
};
struct fdt_reset fdt_syscon_reboot = {
.match_table = syscon_reboot_match,
.init = syscon_reset_init,
};