| /* |
| * 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, |
| }; |