/* * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2023 Ventana Micro Systems Inc. * * Authors: * Anup Patel */ #include #include #include #include #include #include #include #include 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(const 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 }, { }, }; const struct fdt_driver 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 }, { }, }; const struct fdt_driver fdt_syscon_reboot = { .match_table = syscon_reboot_match, .init = syscon_reset_init, };