diff options
Diffstat (limited to 'lib/utils')
| -rw-r--r-- | lib/utils/cache/fdt_andes_llcache.c | 23 | ||||
| -rw-r--r-- | lib/utils/cppc/fdt_cppc_rpmi.c | 44 | ||||
| -rw-r--r-- | lib/utils/hsm/fdt_hsm_andes_atcsmu.c | 64 | ||||
| -rw-r--r-- | lib/utils/mpxy/fdt_mpxy_rpmi_logging.c | 2 | ||||
| -rw-r--r-- | lib/utils/mpxy/fdt_mpxy_rpmi_mm.c | 9 | ||||
| -rw-r--r-- | lib/utils/reset/Kconfig | 4 | ||||
| -rw-r--r-- | lib/utils/reset/fdt_reset_metanoia.c | 72 | ||||
| -rw-r--r-- | lib/utils/reset/objects.mk | 3 | ||||
| -rw-r--r-- | lib/utils/suspend/fdt_suspend_andes_atcsmu.c | 70 |
9 files changed, 224 insertions, 67 deletions
diff --git a/lib/utils/cache/fdt_andes_llcache.c b/lib/utils/cache/fdt_andes_llcache.c index 490503ee..7a61b6ab 100644 --- a/lib/utils/cache/fdt_andes_llcache.c +++ b/lib/utils/cache/fdt_andes_llcache.c @@ -5,8 +5,11 @@ */ #include <sbi/riscv_io.h> +#include <sbi/sbi_domain.h> #include <sbi/sbi_error.h> +#include <sbi/sbi_hart.h> #include <sbi/sbi_heap.h> +#include <sbi/sbi_scratch.h> #include <sbi_utils/cache/fdt_cache.h> #include <sbi_utils/fdt/fdt_driver.h> #include <sbi_utils/hsm/fdt_hsm_andes_atcsmu.h> @@ -102,7 +105,10 @@ static int andes_llcache_enable(struct cache_device *dev, bool enable) } llcache_ctrl = readl(ctrl_addr); - return enable == EXTRACT_FIELD(llcache_ctrl, LLCACHE_REG_CTRL_EN_MASK); + if (enable != EXTRACT_FIELD(llcache_ctrl, LLCACHE_REG_CTRL_EN_MASK)) + return SBI_EFAIL; + + return SBI_OK; } static struct cache_ops andes_llcache_ops = { @@ -113,13 +119,13 @@ static struct cache_ops andes_llcache_ops = { static int andes_llcache_probe(const void *fdt, int nodeoff, const struct fdt_match *match) { int rc; - u64 llcache_base = 0; + u64 llcache_base = 0, size; struct andes_llcache *llcache; struct cache_device *dev; uint32_t llcache_cfg; - rc = fdt_get_node_addr_size(fdt, nodeoff, 0, &llcache_base, NULL); - if (rc < 0 || !llcache_base) + rc = fdt_get_node_addr_size(fdt, nodeoff, 0, &llcache_base, &size); + if (rc < 0 || !llcache_base || !size) return SBI_ENODEV; llcache = sbi_zalloc(sizeof(*llcache)); @@ -148,6 +154,15 @@ static int andes_llcache_probe(const void *fdt, int nodeoff, const struct fdt_ma llcache->status_core_stride = 4; } + if (sbi_hart_has_extension(sbi_scratch_thishart_ptr(), SBI_HART_EXT_SMEPMP)) { + rc = sbi_domain_root_add_memrange( + (unsigned long)llcache->base, (unsigned long)size, PAGE_SIZE, + SBI_DOMAIN_MEMREGION_MMIO | + SBI_DOMAIN_MEMREGION_SHARED_SURW_MRW); + if (rc) + return rc; + } + /* Wait for the hardware initialization done */ while (!andes_llcache_init_done(llcache)) ; diff --git a/lib/utils/cppc/fdt_cppc_rpmi.c b/lib/utils/cppc/fdt_cppc_rpmi.c index 7bd3b0f9..15c89351 100644 --- a/lib/utils/cppc/fdt_cppc_rpmi.c +++ b/lib/utils/cppc/fdt_cppc_rpmi.c @@ -12,6 +12,7 @@ #include <sbi/sbi_cppc.h> #include <sbi/sbi_ecall_interface.h> #include <sbi/sbi_scratch.h> +#include <sbi/sbi_timer.h> #include <sbi_utils/fdt/fdt_driver.h> #include <sbi_utils/fdt/fdt_helper.h> #include <sbi_utils/mailbox/fdt_mailbox.h> @@ -39,6 +40,8 @@ struct rpmi_cppc { ulong fc_db_addr; u64 fc_db_setmask; u64 fc_db_preservemask; + u64 prev_time_csr; + u64 prev_delivered_counter; }; static unsigned long rpmi_cppc_offset; @@ -107,6 +110,7 @@ static void rpmi_cppc_fc_db_trigger(struct rpmi_cppc *cppc) static int rpmi_cppc_read(unsigned long reg, u64 *val) { + u64 curr_freq, curr_time_csr, time_csr_delta, freq; int rc = SBI_SUCCESS; struct rpmi_cppc_read_reg_req req; struct rpmi_cppc_read_reg_resp resp; @@ -116,12 +120,38 @@ static int rpmi_cppc_read(unsigned long reg, u64 *val) req.reg_id = reg; cppc = rpmi_cppc_get_pointer(req.hart_id); - rc = rpmi_normal_request_with_status( - cppc->chan, RPMI_CPPC_SRV_READ_REG, - &req, rpmi_u32_count(req), rpmi_u32_count(req), - &resp, rpmi_u32_count(resp), rpmi_u32_count(resp)); - if (rc) - return rc; + if ((reg == SBI_CPPC_DELIVERED_CTR) && cppc->fc_supported) { +#if __riscv_xlen != 32 + curr_freq = readq((void *)cppc->fc_perf_feedback_addr); +#else + curr_freq = readl((void *)cppc->fc_perf_feedback_addr + 4); + curr_freq <<= 32; + curr_freq |= readl((void *)cppc->fc_perf_feedback_addr); +#endif + curr_time_csr = sbi_timer_value(); + time_csr_delta = curr_time_csr - cppc->prev_time_csr; + cppc->prev_time_csr = curr_time_csr; + + freq = sbi_timer_frequency(); + if (freq) + cppc->prev_delivered_counter += + curr_freq * (time_csr_delta / freq) + + (curr_freq * (time_csr_delta % freq)) / freq; + + resp.data_lo = cpu_to_le32(cppc->prev_delivered_counter & UINT32_MAX); + resp.data_hi = cpu_to_le32(cppc->prev_delivered_counter >> 32); + } else if (reg == SBI_CPPC_REFERENCE_CTR) { + curr_time_csr = sbi_timer_value(); + resp.data_lo = cpu_to_le32(curr_time_csr & UINT32_MAX); + resp.data_hi = cpu_to_le32(curr_time_csr >> 32); + } else { + rc = rpmi_normal_request_with_status( + cppc->chan, RPMI_CPPC_SRV_READ_REG, + &req, rpmi_u32_count(req), rpmi_u32_count(req), + &resp, rpmi_u32_count(resp), rpmi_u32_count(resp)); + if (rc) + return rc; + } #if __riscv_xlen == 32 *val = resp.data_lo; @@ -276,6 +306,8 @@ static int rpmi_cppc_update_hart_scratch(struct mbox_chan *chan) if (!cppc) return SBI_ENOSYS; + cppc->prev_time_csr = 0; + cppc->prev_delivered_counter = 0; cppc->chan = chan; cppc->mode = cppc_mode; cppc->fc_supported = fc_supported; diff --git a/lib/utils/hsm/fdt_hsm_andes_atcsmu.c b/lib/utils/hsm/fdt_hsm_andes_atcsmu.c index 115916d5..2885a983 100644 --- a/lib/utils/hsm/fdt_hsm_andes_atcsmu.c +++ b/lib/utils/hsm/fdt_hsm_andes_atcsmu.c @@ -56,7 +56,7 @@ bool atcsmu_support_sleep_mode(u32 sleep_type, u32 hartid) void atcsmu_set_command(u32 pcs_ctl, u32 hartid) { - writel_relaxed(pcs_ctl, (char *)atcsmu_base + PCSm_CTL_OFFSET(hartid)); + writel(pcs_ctl, (char *)atcsmu_base + PCSm_CTL_OFFSET(hartid)); } int atcsmu_set_reset_vector(u64 wakeup_addr, u32 hartid) @@ -94,42 +94,24 @@ u32 atcsmu_read_scratch(void) return readl_relaxed((char *)atcsmu_base + SCRATCH_PAD_OFFSET); } -bool atcsmu_pcs_is_sleep(u32 hartid, bool deep_sleep) +bool atcsmu_hart_is_sleep(void *opaque) { - u32 pcs_status = readl_relaxed((char *)atcsmu_base + PCSm_STATUS_OFFSET(hartid)); - u32 pd_status = deep_sleep ? PD_STATUS_DEEP_SLEEP : PD_STATUS_LIGHT_SLEEP; + struct atcsmu_sleep_arg *arg = opaque; - if (EXTRACT_FIELD(pcs_status, PD_TYPE_MASK) != PD_TYPE_SLEEP) { - sbi_printf("ATCSMU: hart%d (PCS%d): failed to sleep\n", hartid, hartid + 3); - return false; - } - - if (EXTRACT_FIELD(pcs_status, PD_STATUS_MASK) != pd_status) { - sbi_printf("ATCSMU: hart%d (PCS%d): failed to enter %s sleep\n", - hartid, hartid + 3, deep_sleep ? "deep" : "light"); - return false; - } + u32 pcs_status = readl_relaxed((char *)atcsmu_base + + PCSm_STATUS_OFFSET(arg->hartid)); + u32 pd_status = arg->deep_sleep ? PD_STATUS_DEEP_SLEEP : + PD_STATUS_LIGHT_SLEEP; - return true; + return EXTRACT_FIELD(pcs_status, PD_TYPE_MASK) == PD_TYPE_SLEEP && + EXTRACT_FIELD(pcs_status, PD_STATUS_MASK) == pd_status; } static int ae350_hart_start(u32 hartid, ulong saddr) { u32 hartindex = sbi_hartid_to_hartindex(hartid); - u32 sleep_type = atcsmu_get_sleep_type(hartid); - - /* - * Don't send wakeup command when: - * 1) boot time - * 2) the target hart is non-sleepable 25-series hart0 - * 3) light sleep - */ - if (!sbi_init_count(hartindex) || (is_andes(25) && hartid == 0) || - sleep_type == SBI_SUSP_AE350_LIGHT_SLEEP) - return sbi_ipi_raw_send(hartindex, false); - atcsmu_set_command(WAKEUP_CMD, hartid); - return 0; + return sbi_ipi_raw_send(hartindex, false); } static int ae350_hart_stop(void) @@ -152,18 +134,19 @@ static int ae350_hart_stop(void) /* Prevent the core leaving the WFI mode unexpectedly */ csr_write(CSR_MIE, 0); + atcsmu_set_wakeup_events(PCS_WAKEUP_MSIP_MASK | PCS_WAKEUP_MEIP_MASK, hartid); if (sleep_type == SBI_SUSP_AE350_LIGHT_SLEEP) { - csr_write(CSR_MIE, MIP_MSIP); - atcsmu_set_wakeup_events(PCS_WAKEUP_MSIP_MASK, hartid); + /* Clock-gated only: needs MSI or MEI set to resume past the WFI */ + csr_set(CSR_MIE, MIP_MSIP | MIP_MEIP); atcsmu_set_command(LIGHT_SLEEP_CMD, hartid); } else if (sleep_type == SBI_SUSP_SLEEP_TYPE_SUSPEND) { - atcsmu_set_wakeup_events(0x0, hartid); - atcsmu_set_command(DEEP_SLEEP_CMD, hartid); + /* Power-gated: SMU wakes it via cold reset, interrupts not needed */ rc = atcsmu_set_reset_vector((ulong)ae350_enable_coherency_warmboot, hartid); if (rc) return SBI_EFAIL; ae350_non_ret_save(sbi_scratch_thishart_ptr()); + atcsmu_set_command(DEEP_SLEEP_CMD, hartid); } ae350_disable_coherency(); @@ -184,18 +167,27 @@ static const struct sbi_hsm_device hsm_andes_atcsmu = { static int hsm_andes_atcsmu_probe(const void *fdt, int nodeoff, const struct fdt_match *match) { int poff, rc; - u64 addr; + u64 addr, size; /* Need to find the parent for the address property */ poff = fdt_parent_offset(fdt, nodeoff); if (poff < 0) return SBI_EINVAL; - rc = fdt_get_node_addr_size(fdt, poff, 0, &addr, NULL); - if (rc < 0 || !addr) + rc = fdt_get_node_addr_size(fdt, poff, 0, &addr, &size); + if (rc < 0 || !addr || !size) return SBI_ENODEV; - atcsmu_base = addr; + if (sbi_hart_has_extension(sbi_scratch_thishart_ptr(), SBI_HART_EXT_SMEPMP)) { + rc = sbi_domain_root_add_memrange( + (unsigned long)addr, (unsigned long)size, PAGE_SIZE, + SBI_DOMAIN_MEMREGION_MMIO | + SBI_DOMAIN_MEMREGION_SHARED_SURW_MRW); + if (rc) + return rc; + } + + atcsmu_base = addr; sbi_hsm_set_device(&hsm_andes_atcsmu); return 0; } diff --git a/lib/utils/mpxy/fdt_mpxy_rpmi_logging.c b/lib/utils/mpxy/fdt_mpxy_rpmi_logging.c index e8f085eb..1e92053d 100644 --- a/lib/utils/mpxy/fdt_mpxy_rpmi_logging.c +++ b/lib/utils/mpxy/fdt_mpxy_rpmi_logging.c @@ -11,7 +11,7 @@ static struct mpxy_rpmi_service_data logging_services[] = { [0] = { - .id = RPMI_MM_SRV_ENABLE_NOTIFICATION, + .id = RPMI_LOGGING_SRV_ENABLE_NOTIFICATION, .min_tx_len = sizeof(struct rpmi_enable_notification_req), .max_tx_len = sizeof(struct rpmi_enable_notification_req), .min_rx_len = sizeof(struct rpmi_enable_notification_resp), diff --git a/lib/utils/mpxy/fdt_mpxy_rpmi_mm.c b/lib/utils/mpxy/fdt_mpxy_rpmi_mm.c index d7176aca..7185f3f3 100644 --- a/lib/utils/mpxy/fdt_mpxy_rpmi_mm.c +++ b/lib/utils/mpxy/fdt_mpxy_rpmi_mm.c @@ -15,13 +15,20 @@ static struct rpmi_mm_get_attributes_rsp rsp; static struct mpxy_rpmi_service_data mm_srvcdata[] = { [0] = { + .id = RPMI_MM_SRV_ENABLE_NOTIFICATION, + .min_tx_len = sizeof(struct rpmi_enable_notification_req), + .max_tx_len = sizeof(struct rpmi_enable_notification_req), + .min_rx_len = sizeof(struct rpmi_enable_notification_resp), + .max_rx_len = sizeof(struct rpmi_enable_notification_resp), + }, + [1] = { .id = RPMI_MM_SRV_GET_ATTRIBUTES, .min_tx_len = 0, .max_tx_len = 0, .min_rx_len = sizeof(struct rpmi_mm_get_attributes_rsp), .max_rx_len = sizeof(struct rpmi_mm_get_attributes_rsp), }, - [1] = { + [2] = { .id = RPMI_MM_SRV_COMMUNICATE, .min_tx_len = sizeof(struct rpmi_mm_communicate_req), .max_tx_len = sizeof(struct rpmi_mm_communicate_req), diff --git a/lib/utils/reset/Kconfig b/lib/utils/reset/Kconfig index b2ac120e..8988548c 100644 --- a/lib/utils/reset/Kconfig +++ b/lib/utils/reset/Kconfig @@ -29,6 +29,10 @@ config FDT_RESET_LITEX depends on FDT_GPIO default n +config FDT_RESET_METANOIA + bool "Metanoia FDT reset driver" + default n + config FDT_RESET_RPMI bool "RPMI FDT reset driver" depends on FDT_MAILBOX && RPMI_MAILBOX diff --git a/lib/utils/reset/fdt_reset_metanoia.c b/lib/utils/reset/fdt_reset_metanoia.c new file mode 100644 index 00000000..6f75a893 --- /dev/null +++ b/lib/utils/reset/fdt_reset_metanoia.c @@ -0,0 +1,72 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026 Metanoia Communications Inc. + * + */ + +#include <sbi/riscv_io.h> +#include <sbi/sbi_platform.h> +#include <sbi/sbi_system.h> +#include <sbi_utils/fdt/fdt_helper.h> +#include <sbi_utils/fdt/fdt_driver.h> + +#define SWRSTREQ_CTRL_REG_OFFSET 0x00 +#define SWRSTREQ_REG_OFFSET 0x04 + +struct reset_metanoia_data { + u8 *reg_base; +}; + +static struct reset_metanoia_data reset_data; + +static int metanoia_system_reset_check(u32 type, u32 reason) +{ + switch (type) { + case SBI_SRST_RESET_TYPE_WARM_REBOOT: + case SBI_SRST_RESET_TYPE_COLD_REBOOT: + return 1; + case SBI_SRST_RESET_TYPE_SHUTDOWN: + default: + return 0; + } +} + +static void metanoia_system_reset(u32 type, u32 reason) +{ + writew(0x1, reset_data.reg_base + SWRSTREQ_CTRL_REG_OFFSET); + writew(0x1, reset_data.reg_base + SWRSTREQ_REG_OFFSET); +} + +static struct sbi_system_reset_device metanoia_reset = { + .name = "metanoia-mt2824-reboot", + .system_reset_check = metanoia_system_reset_check, + .system_reset = metanoia_system_reset, +}; + +static int metanoia_reset_init(const void *fdt, int nodeoff, + const struct fdt_match *match) +{ + u64 reg_addr; + int rc; + + rc = fdt_get_node_addr_size(fdt, nodeoff, 0, ®_addr, NULL); + if (rc < 0 || !reg_addr) + return SBI_ENODEV; + + reset_data.reg_base = (u8 *)(ulong)reg_addr; + + sbi_system_reset_add_device(&metanoia_reset); + + return 0; +} + +static const struct fdt_match metanoia_reset_match[] = { + { .compatible = "metanoia,mt2824-reboot" }, + { /* sentinel */ } +}; + +const struct fdt_driver fdt_reset_metanoia = { + .match_table = metanoia_reset_match, + .init = metanoia_reset_init, +}; diff --git a/lib/utils/reset/objects.mk b/lib/utils/reset/objects.mk index 38b4e306..a9b6edad 100644 --- a/lib/utils/reset/objects.mk +++ b/lib/utils/reset/objects.mk @@ -20,6 +20,9 @@ libsbiutils-objs-$(CONFIG_FDT_RESET_HTIF) += reset/fdt_reset_htif.o carray-fdt_early_drivers-$(CONFIG_FDT_RESET_LITEX) += fdt_reset_litex libsbiutils-objs-$(CONFIG_FDT_RESET_LITEX) += reset/fdt_reset_litex.o +carray-fdt_early_drivers-$(CONFIG_FDT_RESET_METANOIA) += fdt_reset_metanoia +libsbiutils-objs-$(CONFIG_FDT_RESET_METANOIA) += reset/fdt_reset_metanoia.o + carray-fdt_early_drivers-$(CONFIG_FDT_RESET_SG2042_HWMON_MCU) += fdt_reset_sg2042_mcu libsbiutils-objs-$(CONFIG_FDT_RESET_SG2042_HWMON_MCU) += reset/fdt_reset_sg2042_hwmon_mcu.o diff --git a/lib/utils/suspend/fdt_suspend_andes_atcsmu.c b/lib/utils/suspend/fdt_suspend_andes_atcsmu.c index 072accc0..86cdf955 100644 --- a/lib/utils/suspend/fdt_suspend_andes_atcsmu.c +++ b/lib/utils/suspend/fdt_suspend_andes_atcsmu.c @@ -12,22 +12,37 @@ #include <sbi/sbi_ecall_interface.h> #include <sbi/sbi_hart.h> #include <sbi/sbi_system.h> +#include <sbi/sbi_timer.h> #include <sbi_utils/cache/fdt_cmo_helper.h> #include <sbi_utils/fdt/fdt_driver.h> #include <sbi_utils/fdt/fdt_helper.h> #include <sbi_utils/hsm/fdt_hsm_andes_atcsmu.h> -static int check_secondary_harts_sleep(u32 hartid, bool deep_sleep) +#define HART_SLEEP_TIMEOUT_MS 1000 + +static int wait_secondary_harts_sleep(u32 hartid, bool deep_sleep) { const struct sbi_domain *dom = &root; unsigned long i; u32 target; + struct atcsmu_sleep_arg arg; + + arg.deep_sleep = deep_sleep; - /* Ensure the secondary harts entering the corresponding sleep state */ + /* Wait for the secondary harts entering the corresponding sleep state */ sbi_hartmask_for_each_hartindex(i, dom->possible_harts) { target = sbi_hartindex_to_hartid(i); - if (target != hartid && !atcsmu_pcs_is_sleep(target, deep_sleep)) - return SBI_EFAIL; + if (target == hartid) + continue; + + arg.hartid = target; + if (!sbi_timer_waitms_until(atcsmu_hart_is_sleep, &arg, + HART_SLEEP_TIMEOUT_MS)) { + sbi_printf("ATCSMU: hart%u (PCS%u): timed out waiting for %s sleep\n", + target, target + 3, + deep_sleep ? "deep" : "light"); + return SBI_ETIMEOUT; + } } return SBI_OK; @@ -42,39 +57,45 @@ static int ae350_system_suspend_check(u32 sleep_type) static int ae350_system_suspend(u32 sleep_type, unsigned long addr) { u32 hartid = current_hartid(); + unsigned long saved_mie; int rc; /* Prevent the core leaving the WFI mode unexpectedly */ + saved_mie = csr_read(CSR_MIE); csr_write(CSR_MIE, 0); - /* - * Only allow the S-mode external interrupts (UART2 and RTC alarm) to - * wake up the primary hart - */ - csr_set(CSR_SIE, MIP_SEIP); + /* SMU wakes the primary hart on RTC alarm / UART2 */ atcsmu_set_wakeup_events(PCS_WAKEUP_RTC_ALARM_MASK | PCS_WAKEUP_UART2_MASK, hartid); if (sleep_type == SBI_SUSP_AE350_LIGHT_SLEEP) { - rc = check_secondary_harts_sleep(hartid, false); + rc = wait_secondary_harts_sleep(hartid, false); if (rc) - return rc; + goto err_restore_mie; + /* Clock-gated only: enable SEI to resume past the WFI */ + csr_set(CSR_MIE, MIP_SEIP); atcsmu_set_command(LIGHT_SLEEP_CMD, hartid); } else if (sleep_type == SBI_SUSP_SLEEP_TYPE_SUSPEND) { - rc = check_secondary_harts_sleep(hartid, true); + rc = wait_secondary_harts_sleep(hartid, true); if (rc) - return rc; + goto err_restore_mie; - atcsmu_set_command(DEEP_SLEEP_CMD, hartid); rc = atcsmu_set_reset_vector((ulong)ae350_enable_coherency_warmboot, hartid); if (rc) - return rc; + goto err_restore_mie; ae350_non_ret_save(sbi_scratch_thishart_ptr()); - fdt_cmo_llc_enable(false); + + /* No LLC is fine; only fail on real errors */ + rc = fdt_cmo_llc_enable(false); + if (rc && rc != SBI_ENODEV) + goto err_discard_save; + rc = fdt_cmo_llc_flush_all(); - if (rc) - return rc; + if (rc && rc != SBI_ENODEV) + goto err_enable_llc; + + atcsmu_set_command(DEEP_SLEEP_CMD, hartid); } ae350_disable_coherency(); @@ -84,6 +105,15 @@ static int ae350_system_suspend(u32 sleep_type, unsigned long addr) ae350_enable_coherency(); return SBI_OK; + +err_enable_llc: + fdt_cmo_llc_enable(true); +err_discard_save: + ae350_non_ret_discard(sbi_scratch_thishart_ptr()); +err_restore_mie: + csr_write(CSR_MIE, saved_mie); + + return rc; } static void ae350_system_resume(void) @@ -91,8 +121,10 @@ static void ae350_system_resume(void) u32 hartid = current_hartid(); u32 sleep_type = atcsmu_get_sleep_type(hartid); - if (sleep_type == SBI_SUSP_SLEEP_TYPE_SUSPEND) + if (sleep_type == SBI_SUSP_SLEEP_TYPE_SUSPEND) { fdt_cmo_llc_enable(true); + ae350_non_ret_restore(sbi_scratch_thishart_ptr()); + } } static struct sbi_system_suspend_device suspend_andes_atcsmu = { |
