* [PATCH 1/5] lib: utils/hsm: factor out ATCSMU code into an HSM driver
2025-12-29 7:19 [PATCH 0/5] Add hart state management and system suspend support for AE350 Ben Zong-You Xie
@ 2025-12-29 7:19 ` Ben Zong-You Xie
2025-12-29 7:19 ` [PATCH 2/5] platform: generic/andes: add CSR save and restore functions for AE350 platform Ben Zong-You Xie
` (4 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Ben Zong-You Xie @ 2025-12-29 7:19 UTC (permalink / raw)
To: opensbi; +Cc: Leo Yu-Chi Liang
Refactor ATCSMU (System Management Unit) support by moving it from a
system utility into a dedicated FDT-based HSM driver.
Key changes include:
- Moving the functions in lib/utils/sys/atcsmu.c into the new HSM driver
- Moving hart start and stop operations on AE350 platform into the new
HSM driver
- Converting the assembly-based functions in sleep.S to C code for the
readability
- Updating the ATCWDT200 driver
Signed-off-by: Ben Zong-You Xie <ben717@andestech.com>
Signed-off-by: Leo Yu-Chi Liang <ycliang@andestech.com>
---
include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h | 49 ++++++
include/sbi_utils/sys/atcsmu.h | 59 -------
lib/utils/hsm/Kconfig | 4 +
lib/utils/hsm/fdt_hsm_andes_atcsmu.c | 166 +++++++++++++++++++
lib/utils/hsm/objects.mk | 5 +-
lib/utils/reset/Kconfig | 2 +-
lib/utils/reset/fdt_reset_atcwdt200.c | 22 +--
lib/utils/sys/Kconfig | 4 -
lib/utils/sys/atcsmu.c | 89 ----------
lib/utils/sys/objects.mk | 1 -
platform/generic/Kconfig | 1 -
platform/generic/andes/ae350.c | 112 +------------
platform/generic/andes/objects.mk | 2 +-
platform/generic/andes/sleep.S | 70 --------
platform/generic/configs/defconfig | 1 +
platform/generic/include/andes/andes.h | 62 +++++++
16 files changed, 301 insertions(+), 348 deletions(-)
create mode 100644 include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h
delete mode 100644 include/sbi_utils/sys/atcsmu.h
create mode 100644 lib/utils/hsm/fdt_hsm_andes_atcsmu.c
delete mode 100644 lib/utils/sys/atcsmu.c
delete mode 100644 platform/generic/andes/sleep.S
diff --git a/include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h b/include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h
new file mode 100644
index 000000000000..881d8b215060
--- /dev/null
+++ b/include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h
@@ -0,0 +1,49 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Andes Technology Corporation
+ */
+
+#ifndef __FDT_HSM_ANDES_ATCSMU_H__
+#define __FDT_HSM_ANDES_ATCSMU_H__
+
+#include <sbi/sbi_types.h>
+
+/* clang-format off */
+
+#define RESET_VEC_LO_OFFSET 0x50
+#define RESET_VEC_HI_OFFSET 0x60
+#define RESET_VEC_8CORE_OFFSET 0x1a0
+#define HARTn_RESET_VEC_LO(n) (RESET_VEC_LO_OFFSET + \
+ ((n) < 4 ? 0 : RESET_VEC_8CORE_OFFSET) + \
+ ((n) * 0x4))
+#define HARTn_RESET_VEC_HI(n) (RESET_VEC_HI_OFFSET + \
+ ((n) < 4 ? 0 : RESET_VEC_8CORE_OFFSET) + \
+ ((n) * 0x4))
+
+#define PCS0_CFG_OFFSET 0x80
+#define PCSm_CFG_OFFSET(i) ((i + 3) * 0x20 + PCS0_CFG_OFFSET)
+#define PCS_CFG_LIGHT_SLEEP BIT(2)
+#define PCS_CFG_DEEP_SLEEP BIT(3)
+
+#define PCS0_SCRATCH_OFFSET 0x84
+#define PCSm_SCRATCH_OFFSET(i) ((i + 3) * 0x20 + PCS0_SCRATCH_OFFSET)
+
+#define PCS0_WE_OFFSET 0x90
+#define PCSm_WE_OFFSET(i) ((i + 3) * 0x20 + PCS0_WE_OFFSET)
+
+#define PCS0_CTL_OFFSET 0x94
+#define PCSm_CTL_OFFSET(i) ((i + 3) * 0x20 + PCS0_CTL_OFFSET)
+#define LIGHT_SLEEP_CMD 0x3
+#define WAKEUP_CMD 0x8
+#define DEEP_SLEEP_CMD 0xb
+
+/* clang-format on */
+
+void atcsmu_set_wakeup_events(u32 events, u32 hartid);
+bool atcsmu_support_sleep_mode(u32 sleep_type, u32 hartid);
+void atcsmu_set_command(u32 pcs_ctl, u32 hartid);
+int atcsmu_set_reset_vector(u64 wakeup_addr, u32 hartid);
+u32 atcsmu_get_sleep_type(u32 hartid);
+
+#endif
diff --git a/include/sbi_utils/sys/atcsmu.h b/include/sbi_utils/sys/atcsmu.h
deleted file mode 100644
index 07be22eef626..000000000000
--- a/include/sbi_utils/sys/atcsmu.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * SPDX-License-Identifier: BSD-3-Clause
- *
- * Copyright (c) 2023 Andes Technology Corporation
- */
-
-#ifndef _SYS_ATCSMU_H
-#define _SYS_ATCSMU_H
-
-#include <sbi/sbi_types.h>
-
-/* clang-format off */
-
-#define PCS0_WE_OFFSET 0x90
-#define PCSm_WE_OFFSET(i) ((i + 3) * 0x20 + PCS0_WE_OFFSET)
-
-#define PCS0_CTL_OFFSET 0x94
-#define PCSm_CTL_OFFSET(i) ((i + 3) * 0x20 + PCS0_CTL_OFFSET)
-#define PCS_CTL_CMD_SHIFT 0
-#define PCS_CTL_PARAM_SHIFT 3
-#define SLEEP_CMD 0x3
-#define WAKEUP_CMD (0x0 | (1 << PCS_CTL_PARAM_SHIFT))
-#define LIGHTSLEEP_MODE 0
-#define DEEPSLEEP_MODE 1
-#define LIGHT_SLEEP_CMD (SLEEP_CMD | (LIGHTSLEEP_MODE << PCS_CTL_PARAM_SHIFT))
-#define DEEP_SLEEP_CMD (SLEEP_CMD | (DEEPSLEEP_MODE << PCS_CTL_PARAM_SHIFT))
-
-#define PCS0_CFG_OFFSET 0x80
-#define PCSm_CFG_OFFSET(i) ((i + 3) * 0x20 + PCS0_CFG_OFFSET)
-#define PCS_CFG_LIGHT_SLEEP_SHIFT 2
-#define PCS_CFG_LIGHT_SLEEP (1 << PCS_CFG_LIGHT_SLEEP_SHIFT)
-#define PCS_CFG_DEEP_SLEEP_SHIFT 3
-#define PCS_CFG_DEEP_SLEEP (1 << PCS_CFG_DEEP_SLEEP_SHIFT)
-
-#define RESET_VEC_LO_OFFSET 0x50
-#define RESET_VEC_HI_OFFSET 0x60
-#define RESET_VEC_8CORE_OFFSET 0x1a0
-#define HARTn_RESET_VEC_LO(n) (RESET_VEC_LO_OFFSET + \
- ((n) < 4 ? 0 : RESET_VEC_8CORE_OFFSET) + \
- ((n) * 0x4))
-#define HARTn_RESET_VEC_HI(n) (RESET_VEC_HI_OFFSET + \
- ((n) < 4 ? 0 : RESET_VEC_8CORE_OFFSET) + \
- ((n) * 0x4))
-
-#define PCS_MAX_NR 8
-#define FLASH_BASE 0x80000000ULL
-
-/* clang-format on */
-
-struct smu_data {
- unsigned long addr;
-};
-
-int smu_set_wakeup_events(struct smu_data *smu, u32 events, u32 hartid);
-bool smu_support_sleep_mode(struct smu_data *smu, u32 sleep_mode, u32 hartid);
-int smu_set_command(struct smu_data *smu, u32 pcs_ctl, u32 hartid);
-int smu_set_reset_vector(struct smu_data *smu, ulong wakeup_addr, u32 hartid);
-
-#endif /* _SYS_ATCSMU_H */
diff --git a/lib/utils/hsm/Kconfig b/lib/utils/hsm/Kconfig
index 94973c8fd7bd..1dfb243e3109 100644
--- a/lib/utils/hsm/Kconfig
+++ b/lib/utils/hsm/Kconfig
@@ -9,6 +9,10 @@ config FDT_HSM
if FDT_HSM
+config FDT_HSM_ANDES_ATCSMU
+ bool "FDT Andes ATCSMU driver"
+ default n
+
config FDT_HSM_RPMI
bool "FDT RPMI HSM driver"
depends on FDT_MAILBOX && RPMI_MAILBOX
diff --git a/lib/utils/hsm/fdt_hsm_andes_atcsmu.c b/lib/utils/hsm/fdt_hsm_andes_atcsmu.c
new file mode 100644
index 000000000000..ee7873dc6d00
--- /dev/null
+++ b/lib/utils/hsm/fdt_hsm_andes_atcsmu.c
@@ -0,0 +1,166 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Andes Technology Corporation
+ */
+
+#include <andes/andes.h>
+#include <libfdt.h>
+#include <sbi/riscv_io.h>
+#include <sbi/sbi_console.h>
+#include <sbi/sbi_ecall_interface.h>
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_hart.h>
+#include <sbi/sbi_hsm.h>
+#include <sbi/sbi_init.h>
+#include <sbi/sbi_ipi.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 uint64_t atcsmu_base;
+
+void atcsmu_set_wakeup_events(u32 events, u32 hartid)
+{
+ writel_relaxed(events, (char *)atcsmu_base + PCSm_WE_OFFSET(hartid));
+}
+
+bool atcsmu_support_sleep_mode(u32 sleep_type, u32 hartid)
+{
+ u32 pcs_cfg;
+ u32 mask;
+ const char *sleep_mode;
+
+ pcs_cfg = readl_relaxed((char *)atcsmu_base + PCSm_CFG_OFFSET(hartid));
+ switch (sleep_type) {
+ case SBI_SUSP_AE350_LIGHT_SLEEP:
+ mask = PCS_CFG_LIGHT_SLEEP;
+ sleep_mode = "light sleep";
+ break;
+ case SBI_SUSP_SLEEP_TYPE_SUSPEND:
+ mask = PCS_CFG_DEEP_SLEEP;
+ sleep_mode = "deep sleep";
+ break;
+ default:
+ return false;
+ }
+
+ if (!EXTRACT_FIELD(pcs_cfg, mask)) {
+ sbi_printf("ATCSMU: hart%d (PCS%d) does not support %s mode\n",
+ hartid, hartid + 3, sleep_mode);
+ return false;
+ }
+
+ return true;
+}
+
+void atcsmu_set_command(u32 pcs_ctl, u32 hartid)
+{
+ writel_relaxed(pcs_ctl, (char *)atcsmu_base + PCSm_CTL_OFFSET(hartid));
+}
+
+int atcsmu_set_reset_vector(u64 wakeup_addr, u32 hartid)
+{
+ u32 vec_lo;
+ u32 vec_hi;
+ u64 reset_vector;
+
+ writel((u32)wakeup_addr, (char *)atcsmu_base + HARTn_RESET_VEC_LO(hartid));
+ writel((u32)(wakeup_addr >> 32), (char *)atcsmu_base + HARTn_RESET_VEC_HI(hartid));
+ vec_lo = readl((char *)atcsmu_base + HARTn_RESET_VEC_LO(hartid));
+ vec_hi = readl((char *)atcsmu_base + HARTn_RESET_VEC_HI(hartid));
+ reset_vector = (u64)vec_hi << 32 | vec_lo;
+ if (reset_vector != wakeup_addr) {
+ sbi_printf("ATCSMU: hart%d (PCS%d): failed to program the reset vector\n",
+ hartid, hartid + 3);
+ return SBI_EFAIL;
+ }
+
+ return SBI_OK;
+}
+
+u32 atcsmu_get_sleep_type(u32 hartid)
+{
+ return readl_relaxed((char *)atcsmu_base + PCSm_SCRATCH_OFFSET(hartid));
+}
+
+static int ae350_hart_start(u32 hartid, ulong saddr)
+{
+ u32 hartindex = sbi_hartid_to_hartindex(hartid);
+
+ /*
+ * Don't send wakeup command when:
+ * 1) boot time
+ * 2) the target hart is non-sleepable 25-series hart0
+ */
+ if (!sbi_init_count(hartindex) || (is_andes(25) && hartid == 0))
+ return sbi_ipi_raw_send(hartindex, false);
+
+ atcsmu_set_command(WAKEUP_CMD, hartid);
+ return 0;
+}
+
+static int ae350_hart_stop(void)
+{
+ u32 hartid = current_hartid();
+ u32 sleep_type = atcsmu_get_sleep_type(hartid);
+ int rc;
+
+ /*
+ * For Andes AX25MP, the hart0 shares power domain with the last level
+ * cache. Instead of turning it off, it should fall through and jump to
+ * warmboot_addr.
+ */
+ if (is_andes(25) && hartid == 0)
+ return SBI_ENOTSUPP;
+
+ if (!atcsmu_support_sleep_mode(sleep_type, hartid))
+ return SBI_ENOTSUPP;
+
+ /* Prevent the core leaving the WFI mode unexpectedly */
+ csr_write(CSR_MIE, 0);
+
+ atcsmu_set_wakeup_events(0x0, hartid);
+ atcsmu_set_command(DEEP_SLEEP_CMD, hartid);
+ rc = atcsmu_set_reset_vector((u64)ae350_enable_coherency_warmboot, hartid);
+ if (rc)
+ return SBI_EFAIL;
+
+ ae350_disable_coherency();
+ wfi();
+ return 0;
+}
+
+static const struct sbi_hsm_device hsm_andes_atcsmu = {
+ .name = "andes_atcsmu",
+ .hart_start = ae350_hart_start,
+ .hart_stop = ae350_hart_stop,
+};
+
+static int hsm_andes_atcsmu_probe(const void *fdt, int nodeoff, const struct fdt_match *match)
+{
+ int poff;
+ int rc;
+
+ /* 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, &atcsmu_base, NULL);
+ if (rc < 0 || !atcsmu_base)
+ return SBI_ENODEV;
+
+ sbi_hsm_set_device(&hsm_andes_atcsmu);
+ return 0;
+}
+
+static const struct fdt_match hsm_andes_atcsmu_match[] = {
+ { .compatible = "andestech,atcsmu-hsm" },
+ { },
+};
+
+const struct fdt_driver fdt_hsm_andes_atcsmu = {
+ .match_table = hsm_andes_atcsmu_match,
+ .init = hsm_andes_atcsmu_probe,
+};
diff --git a/lib/utils/hsm/objects.mk b/lib/utils/hsm/objects.mk
index 0d0054494f18..f76af03c5c0a 100644
--- a/lib/utils/hsm/objects.mk
+++ b/lib/utils/hsm/objects.mk
@@ -7,6 +7,9 @@
# Anup Patel <apatel@ventanamicro.com>
#
+carray-fdt_early_drivers-$(CONFIG_FDT_HSM_ANDES_ATCSMU) += fdt_hsm_andes_atcsmu
+libsbiutils-objs-$(CONFIG_FDT_HSM_ANDES_ATCSMU) += hsm/fdt_hsm_andes_atcsmu.o
+
carray-fdt_early_drivers-$(CONFIG_FDT_HSM_RPMI) += fdt_hsm_rpmi
libsbiutils-objs-$(CONFIG_FDT_HSM_RPMI) += hsm/fdt_hsm_rpmi.o
@@ -14,4 +17,4 @@ carray-fdt_early_drivers-$(CONFIG_FDT_HSM_SPACEMIT) += fdt_hsm_spacemit
libsbiutils-objs-$(CONFIG_FDT_HSM_SPACEMIT) += hsm/fdt_hsm_spacemit.o
carray-fdt_early_drivers-$(CONFIG_FDT_HSM_SIFIVE_TMC0) += fdt_hsm_sifive_tmc0
-libsbiutils-objs-$(CONFIG_FDT_HSM_SIFIVE_TMC0) += hsm/fdt_hsm_sifive_tmc0.o
\ No newline at end of file
+libsbiutils-objs-$(CONFIG_FDT_HSM_SIFIVE_TMC0) += hsm/fdt_hsm_sifive_tmc0.o
diff --git a/lib/utils/reset/Kconfig b/lib/utils/reset/Kconfig
index 68e667163b5d..4835921f4b90 100644
--- a/lib/utils/reset/Kconfig
+++ b/lib/utils/reset/Kconfig
@@ -11,7 +11,7 @@ if FDT_RESET
config FDT_RESET_ATCWDT200
bool "Andes WDT FDT reset driver"
- depends on SYS_ATCSMU
+ depends on FDT_HSM_ANDES_ATCSMU
default n
config FDT_RESET_GPIO
diff --git a/lib/utils/reset/fdt_reset_atcwdt200.c b/lib/utils/reset/fdt_reset_atcwdt200.c
index 2304582a9513..5dad8ac96d48 100644
--- a/lib/utils/reset/fdt_reset_atcwdt200.c
+++ b/lib/utils/reset/fdt_reset_atcwdt200.c
@@ -1,10 +1,7 @@
/*
* SPDX-License-Identifier: BSD-2-Clause
*
- * Copyright (c) 2022 Andes Technology Corporation
- *
- * Authors:
- * Yu Chien Peter Lin <peterlin@andestech.com>
+ * Copyright (c) 2025 Andes Technology Corporation
*/
#include <libfdt.h>
@@ -15,7 +12,7 @@
#include <sbi/sbi_system.h>
#include <sbi_utils/fdt/fdt_driver.h>
#include <sbi_utils/fdt/fdt_helper.h>
-#include <sbi_utils/sys/atcsmu.h>
+#include <sbi_utils/hsm/fdt_hsm_andes_atcsmu.h>
#define ATCWDT200_WP_NUM 0x5aa5
#define WREN_REG 0x18
@@ -41,8 +38,9 @@
#define CLK_PCLK (1 << 1)
#define WDT_EN (1 << 0)
+#define AE350_FLASH_BASE 0x80000000
+
static volatile char *wdt_addr = NULL;
-static struct smu_data smu = { 0 };
static int ae350_system_reset_check(u32 type, u32 reason)
{
@@ -59,7 +57,7 @@ static int ae350_system_reset_check(u32 type, u32 reason)
static void ae350_system_reset(u32 type, u32 reason)
{
sbi_for_each_hartindex(i)
- if (smu_set_reset_vector(&smu, FLASH_BASE, i))
+ if (atcsmu_set_reset_vector(AE350_FLASH_BASE, i))
goto fail;
/* Program WDT control register */
@@ -88,16 +86,6 @@ static int atcwdt200_reset_init(const void *fdt, int nodeoff,
return SBI_ENODEV;
wdt_addr = (volatile char *)(unsigned long)reg_addr;
-
- /*
- * The reset device requires smu to program the reset
- * vector for each hart.
- */
- if (fdt_parse_compat_addr(fdt, ®_addr, "andestech,atcsmu"))
- return SBI_ENODEV;
-
- smu.addr = (unsigned long)reg_addr;
-
sbi_system_reset_add_device(&atcwdt200_reset);
return 0;
diff --git a/lib/utils/sys/Kconfig b/lib/utils/sys/Kconfig
index a22191cd7c26..fc388665524d 100644
--- a/lib/utils/sys/Kconfig
+++ b/lib/utils/sys/Kconfig
@@ -2,10 +2,6 @@
menu "System Device Support"
-config SYS_ATCSMU
- bool "Andes System Management Unit (SMU) support"
- default n
-
config SYS_HTIF
bool "Host transfere interface (HTIF) support"
default n
diff --git a/lib/utils/sys/atcsmu.c b/lib/utils/sys/atcsmu.c
deleted file mode 100644
index 2cba0eb73e7e..000000000000
--- a/lib/utils/sys/atcsmu.c
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * SPDX-License-Identifier: BSD-3-Clause
- *
- * Copyright (c) 2023 Andes Technology Corporation
- *
- * Authors:
- * Yu Chien Peter Lin <peterlin@andestech.com>
- */
-
-#include <sbi_utils/sys/atcsmu.h>
-#include <sbi/riscv_io.h>
-#include <sbi/sbi_console.h>
-#include <sbi/sbi_error.h>
-#include <sbi/sbi_bitops.h>
-
-inline int smu_set_wakeup_events(struct smu_data *smu, u32 events, u32 hartid)
-{
- if (smu) {
- writel(events, (void *)(smu->addr + PCSm_WE_OFFSET(hartid)));
- return 0;
- } else
- return SBI_EINVAL;
-}
-
-inline bool smu_support_sleep_mode(struct smu_data *smu, u32 sleep_mode,
- u32 hartid)
-{
- u32 pcs_cfg;
-
- if (!smu) {
- sbi_printf("%s(): Failed to access smu_data\n", __func__);
- return false;
- }
-
- pcs_cfg = readl((void *)(smu->addr + PCSm_CFG_OFFSET(hartid)));
-
- switch (sleep_mode) {
- case LIGHTSLEEP_MODE:
- if (EXTRACT_FIELD(pcs_cfg, PCS_CFG_LIGHT_SLEEP) == 0) {
- sbi_printf("SMU: hart%d (PCS%d) does not support light sleep mode\n",
- hartid, hartid + 3);
- return false;
- }
- break;
- case DEEPSLEEP_MODE:
- if (EXTRACT_FIELD(pcs_cfg, PCS_CFG_DEEP_SLEEP) == 0) {
- sbi_printf("SMU: hart%d (PCS%d) does not support deep sleep mode\n",
- hartid, hartid + 3);
- return false;
- }
- break;
- }
-
- return true;
-}
-
-inline int smu_set_command(struct smu_data *smu, u32 pcs_ctl, u32 hartid)
-{
- if (smu) {
- writel(pcs_ctl, (void *)(smu->addr + PCSm_CTL_OFFSET(hartid)));
- return 0;
- } else
- return SBI_EINVAL;
-}
-
-inline int smu_set_reset_vector(struct smu_data *smu, ulong wakeup_addr,
- u32 hartid)
-{
- u32 vec_lo, vec_hi;
- u64 reset_vector;
-
- if (!smu)
- return SBI_EINVAL;
-
- writel(wakeup_addr, (void *)(smu->addr + HARTn_RESET_VEC_LO(hartid)));
- writel((u64)wakeup_addr >> 32,
- (void *)(smu->addr + HARTn_RESET_VEC_HI(hartid)));
-
- vec_lo = readl((void *)(smu->addr + HARTn_RESET_VEC_LO(hartid)));
- vec_hi = readl((void *)(smu->addr + HARTn_RESET_VEC_HI(hartid)));
- reset_vector = ((u64)vec_hi << 32) | vec_lo;
-
- if (reset_vector != (u64)wakeup_addr) {
- sbi_printf("hart%d (PCS%d): Failed to program the reset vector.\n",
- hartid, hartid + 3);
- return SBI_EFAIL;
- } else
- return 0;
-}
diff --git a/lib/utils/sys/objects.mk b/lib/utils/sys/objects.mk
index 409d7e8c7ef6..d9c67077a3a6 100644
--- a/lib/utils/sys/objects.mk
+++ b/lib/utils/sys/objects.mk
@@ -8,4 +8,3 @@
#
libsbiutils-objs-$(CONFIG_SYS_HTIF) += sys/htif.o
-libsbiutils-objs-$(CONFIG_SYS_ATCSMU) += sys/atcsmu.o
diff --git a/platform/generic/Kconfig b/platform/generic/Kconfig
index 716fab42a7b7..0c11fbd2c29f 100644
--- a/platform/generic/Kconfig
+++ b/platform/generic/Kconfig
@@ -36,7 +36,6 @@ config PLATFORM_ALLWINNER_D1
config PLATFORM_ANDES_AE350
bool "Andes AE350 support"
- select SYS_ATCSMU
select ANDES_PMU
select ANDES_PMA
default n
diff --git a/platform/generic/andes/ae350.c b/platform/generic/andes/ae350.c
index 0808065745af..fa3f03685bba 100644
--- a/platform/generic/andes/ae350.c
+++ b/platform/generic/andes/ae350.c
@@ -1,121 +1,25 @@
/*
* SPDX-License-Identifier: BSD-2-Clause
*
- * Copyright (c) 2022 Andes Technology Corporation
- *
- * Authors:
- * Yu Chien Peter Lin <peterlin@andestech.com>
+ * Copyright (c) 2025 Andes Technology Corporation
*/
-#include <platform_override.h>
-#include <andes/andes_pmu.h>
-#include <sbi_utils/fdt/fdt_helper.h>
-#include <sbi_utils/fdt/fdt_fixup.h>
-#include <sbi_utils/sys/atcsmu.h>
-#include <sbi/riscv_asm.h>
-#include <sbi/sbi_bitops.h>
-#include <sbi/sbi_error.h>
-#include <sbi/sbi_hsm.h>
-#include <sbi/sbi_ipi.h>
-#include <sbi/sbi_init.h>
#include <andes/andes.h>
+#include <andes/andes_pmu.h>
#include <andes/andes_sbi.h>
+#include <platform_override.h>
+#include <sbi_utils/fdt/fdt_helper.h>
-static struct smu_data smu = { 0 };
-extern void __ae350_enable_coherency_warmboot(void);
-extern void __ae350_disable_coherency(void);
-
-static int ae350_hart_start(u32 hartid, ulong saddr)
-{
- u32 hartindex = sbi_hartid_to_hartindex(hartid);
-
- /*
- * Don't send wakeup command when:
- * 1) boot-time
- * 2) the target hart is non-sleepable 25-series hart0
- */
- if (!sbi_init_count(hartindex) || (is_andes(25) && hartid == 0))
- return sbi_ipi_raw_send(hartindex, false);
-
- /* Write wakeup command to the sleep hart */
- smu_set_command(&smu, WAKEUP_CMD, hartid);
-
- return 0;
-}
-
-static int ae350_hart_stop(void)
-{
- int rc;
- u32 hartid = current_hartid();
-
- /**
- * For Andes AX25MP, the hart0 shares power domain with
- * L2-cache, instead of turning it off, it should fall
- * through and jump to warmboot_addr.
- */
- if (is_andes(25) && hartid == 0)
- return SBI_ENOTSUPP;
-
- if (!smu_support_sleep_mode(&smu, DEEPSLEEP_MODE, hartid))
- return SBI_ENOTSUPP;
-
- /**
- * disable all events, the current hart will be
- * woken up from reset vector when other hart
- * writes its PCS (power control slot) control
- * register
- */
- smu_set_wakeup_events(&smu, 0x0, hartid);
- smu_set_command(&smu, DEEP_SLEEP_CMD, hartid);
-
- rc = smu_set_reset_vector(&smu,
- (ulong)__ae350_enable_coherency_warmboot,
- hartid);
- if (rc)
- goto fail;
-
- __ae350_disable_coherency();
-
- wfi();
-
-fail:
- /* It should never reach here */
- sbi_hart_hang();
- return 0;
-}
-
-static const struct sbi_hsm_device andes_smu = {
- .name = "andes_smu",
- .hart_start = ae350_hart_start,
- .hart_stop = ae350_hart_stop,
-};
-
-static void ae350_hsm_device_init(const void *fdt)
-{
- int rc;
-
- rc = fdt_parse_compat_addr(fdt, (uint64_t *)&smu.addr,
- "andestech,atcsmu");
-
- if (!rc) {
- sbi_hsm_set_device(&andes_smu);
- }
-}
+extern void _start_warm(void);
-static int ae350_final_init(bool cold_boot)
+void ae350_enable_coherency_warmboot(void)
{
- if (cold_boot) {
- const void *fdt = fdt_get_address();
-
- ae350_hsm_device_init(fdt);
- }
-
- return generic_final_init(cold_boot);
+ ae350_enable_coherency();
+ _start_warm();
}
static int ae350_platform_init(const void *fdt, int nodeoff, const struct fdt_match *match)
{
- generic_platform_ops.final_init = ae350_final_init;
generic_platform_ops.extensions_init = andes_pmu_extensions_init;
generic_platform_ops.pmu_init = andes_pmu_init;
generic_platform_ops.vendor_ext_provider = andes_sbi_vendor_ext_provider;
diff --git a/platform/generic/andes/objects.mk b/platform/generic/andes/objects.mk
index f85ad48162b2..660546c312fe 100644
--- a/platform/generic/andes/objects.mk
+++ b/platform/generic/andes/objects.mk
@@ -3,7 +3,7 @@
#
carray-platform_override_modules-$(CONFIG_PLATFORM_ANDES_AE350) += andes_ae350
-platform-objs-$(CONFIG_PLATFORM_ANDES_AE350) += andes/ae350.o andes/sleep.o
+platform-objs-$(CONFIG_PLATFORM_ANDES_AE350) += andes/ae350.o
carray-platform_override_modules-$(CONFIG_PLATFORM_ANDES_QILAI) += andes_qilai
platform-objs-$(CONFIG_PLATFORM_ANDES_QILAI) += andes/qilai.o
diff --git a/platform/generic/andes/sleep.S b/platform/generic/andes/sleep.S
deleted file mode 100644
index 361aff3a6024..000000000000
--- a/platform/generic/andes/sleep.S
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * SPDX-License-Identifier: BSD-2-Clause
- *
- * Copyright (c) 2023 Andes Technology Corporation
- *
- * Authors:
- * Yu Chien Peter Lin <peterlin@andestech.com>
- */
-
-#include <sbi/riscv_encoding.h>
-#include <sbi/riscv_asm.h>
-#include <andes/andes.h>
-
- .section .text, "ax", %progbits
- .align 3
- .global __ae350_disable_coherency
-__ae350_disable_coherency:
- /* flush d-cache */
- csrw CSR_MCCTLCOMMAND, 0x6
- /* disable i/d-cache */
- csrc CSR_MCACHE_CTL, 0x3
- /* disable d-cache coherency */
- lui t1, 0x80
- csrc CSR_MCACHE_CTL, t1
- /*
- * wait for mcache_ctl.DC_COHSTA to be cleared,
- * the bit is hard-wired 0 on platforms w/o CM
- * (Coherence Manager)
- */
-check_cm_disabled:
- csrr t1, CSR_MCACHE_CTL
- srli t1, t1, 20
- andi t1, t1, 0x1
- bnez t1, check_cm_disabled
-
- ret
-
- .section .text, "ax", %progbits
- .align 3
- .global __ae350_enable_coherency
-__ae350_enable_coherency:
- /* enable d-cache coherency */
- lui t1, 0x80
- csrs CSR_MCACHE_CTL, t1
- /*
- * mcache_ctl.DC_COHEN is hard-wired 0 on platforms
- * w/o CM support
- */
- csrr t1, CSR_MCACHE_CTL
- srli t1, t1, 19
- andi t1, t1, 0x1
- beqz t1, enable_L1_cache
- /* wait for mcache_ctl.DC_COHSTA to be set */
-check_cm_enabled:
- csrr t1, CSR_MCACHE_CTL
- srli t1, t1, 20
- andi t1, t1, 0x1
- beqz t1, check_cm_enabled
-enable_L1_cache:
- /* enable i/d-cache */
- csrs CSR_MCACHE_CTL, 0x3
-
- ret
-
- .section .text, "ax", %progbits
- .align 3
- .global __ae350_enable_coherency_warmboot
-__ae350_enable_coherency_warmboot:
- call ra, __ae350_enable_coherency
- j _start_warm
diff --git a/platform/generic/configs/defconfig b/platform/generic/configs/defconfig
index aab1560f2399..d85da930edbc 100644
--- a/platform/generic/configs/defconfig
+++ b/platform/generic/configs/defconfig
@@ -23,6 +23,7 @@ CONFIG_FDT_GPIO_DESIGNWARE=y
CONFIG_FDT_GPIO_SIFIVE=y
CONFIG_FDT_GPIO_STARFIVE=y
CONFIG_FDT_HSM=y
+CONFIG_FDT_HSM_ANDES_ATCSMU=y
CONFIG_FDT_HSM_RPMI=y
CONFIG_FDT_HSM_SIFIVE_TMC0=y
CONFIG_FDT_I2C=y
diff --git a/platform/generic/include/andes/andes.h b/platform/generic/include/andes/andes.h
index bfedf034c902..1b5893925c10 100644
--- a/platform/generic/include/andes/andes.h
+++ b/platform/generic/include/andes/andes.h
@@ -6,6 +6,9 @@
#ifndef _RISCV_ANDES_H
#define _RISCV_ANDES_H
+#include <sbi/sbi_bitops.h>
+#include <sbi/sbi_scratch.h>
+
/* Memory and Miscellaneous Registers */
#define CSR_MCACHE_CTL 0x7ca
#define CSR_MCCTLCOMMAND 0x7cc
@@ -43,13 +46,23 @@
#define MMSC_IOCP_OFFSET 47
#define MMSC_IOCP_MASK (1ULL << MMSC_IOCP_OFFSET)
+#define MCACHE_CTL_IC_EN_MASK BIT(0)
+#define MCACHE_CTL_DC_EN_MASK BIT(1)
#define MCACHE_CTL_CCTL_SUEN_OFFSET 8
#define MCACHE_CTL_CCTL_SUEN_MASK (1 << MCACHE_CTL_CCTL_SUEN_OFFSET)
+#define MCACHE_CTL_DC_COHEN_MASK BIT(19)
+#define MCACHE_CTL_DC_COHSTA_MASK BIT(20)
/* Performance monitor */
#define MMSC_CFG_PMNDS_MASK (1 << 15)
#define MIP_PMOVI (1 << 18)
+/* Cache control commands */
+#define MCCTLCOMMAND_L1D_WBINVAL_ALL 6
+
+/* AE350 platform specific sleep types */
+#define SBI_SUSP_AE350_LIGHT_SLEEP SBI_SUSP_PLATFORM_SLEEP_START
+
#ifndef __ASSEMBLER__
#define is_andes(series) \
@@ -67,4 +80,53 @@
#endif /* __ASSEMBLER__ */
+void ae350_enable_coherency_warmboot(void);
+
+/*
+ * On Andes 4X-series CPUs, disabling the L1 data cache causes the CPU to fetch
+ * data directly from RAM. However, L1 cache flushes write data back to the
+ * Last Level Cache (LLC). This discrepancy can lead to return address
+ * corruption on the stack. To prevent this, the following functions must
+ * be inlined.
+ */
+static inline void ae350_disable_coherency(void)
+{
+ /*
+ * To disable cache coherency of a core in AE350 platform, follow below steps:
+ *
+ * 1) Disable I/D-Cache
+ * 2) Write back and invalidate D-Cache
+ * 3) Disable D-Cache coherency
+ * 4) Wait for D-Cache disengaged from the coherence management
+ */
+ csr_clear(CSR_MCACHE_CTL, MCACHE_CTL_IC_EN_MASK | MCACHE_CTL_DC_EN_MASK);
+ csr_write(CSR_MCCTLCOMMAND, MCCTLCOMMAND_L1D_WBINVAL_ALL);
+ csr_clear(CSR_MCACHE_CTL, MCACHE_CTL_DC_COHEN_MASK);
+ while (csr_read(CSR_MCACHE_CTL) & MCACHE_CTL_DC_COHSTA_MASK)
+ ;
+}
+
+static inline void ae350_enable_coherency(void)
+{
+ /*
+ * To enable cache coherency of a core in AE350 platform, follow below steps:
+ *
+ * 1) Enable D-Cache coherency
+ * 2) Wait for D-Cache engaging in the coherence management
+ * 3) Enable I/D-Cache
+ */
+ csr_set(CSR_MCACHE_CTL, MCACHE_CTL_DC_COHEN_MASK);
+
+ /*
+ * mcache_ctl.DC_COHEN is hardwired to 0 if there is no coherence
+ * manager. In such situation, just enable the I/D-Cache to prevent
+ * permanently being stuck in the while loop.
+ */
+ if (csr_read(CSR_MCACHE_CTL) & MCACHE_CTL_DC_COHEN_MASK)
+ while (!(csr_read(CSR_MCACHE_CTL) & MCACHE_CTL_DC_COHSTA_MASK))
+ ;
+
+ csr_set(CSR_MCACHE_CTL, MCACHE_CTL_IC_EN_MASK | MCACHE_CTL_DC_EN_MASK);
+}
+
#endif /* _RISCV_ANDES_H */
--
2.34.1
--
opensbi mailing list
opensbi@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/opensbi
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 4/5] lib: utils/cache: add Andes last level cache controller
2025-12-29 7:19 [PATCH 0/5] Add hart state management and system suspend support for AE350 Ben Zong-You Xie
` (2 preceding siblings ...)
2025-12-29 7:19 ` [PATCH 3/5] lib: utils/cache: add cache enable function Ben Zong-You Xie
@ 2025-12-29 7:19 ` Ben Zong-You Xie
2025-12-29 7:19 ` [PATCH 5/5] lib: utils/suspend: add Andes ATCSMU suspend driver Ben Zong-You Xie
2026-02-11 6:24 ` [PATCH 0/5] Add hart state management and system suspend support for AE350 Anup Patel
5 siblings, 0 replies; 9+ messages in thread
From: Ben Zong-You Xie @ 2025-12-29 7:19 UTC (permalink / raw)
To: opensbi
Introduce a FDT-based driver for the Andes Last Level Cache (LLC)
controller to support cache maintenance operations.
Signed-off-by: Ben Zong-You Xie <ben717@andestech.com>
---
include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h | 15 ++
lib/utils/cache/Kconfig | 5 +
lib/utils/cache/fdt_andes_llcache.c | 166 +++++++++++++++++++
lib/utils/cache/objects.mk | 3 +
lib/utils/hsm/fdt_hsm_andes_atcsmu.c | 10 ++
platform/generic/configs/defconfig | 1 +
6 files changed, 200 insertions(+)
create mode 100644 lib/utils/cache/fdt_andes_llcache.c
diff --git a/include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h b/include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h
index 881d8b215060..ef851b7501b5 100644
--- a/include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h
+++ b/include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h
@@ -11,6 +11,8 @@
/* clang-format off */
+#define SCRATCH_PAD_OFFSET 0x40
+
#define RESET_VEC_LO_OFFSET 0x50
#define RESET_VEC_HI_OFFSET 0x60
#define RESET_VEC_8CORE_OFFSET 0x1a0
@@ -31,6 +33,9 @@
#define PCS0_WE_OFFSET 0x90
#define PCSm_WE_OFFSET(i) ((i + 3) * 0x20 + PCS0_WE_OFFSET)
+#define PCS_WAKEUP_RTC_ALARM_MASK BIT(2)
+#define PCS_WAKEUP_UART2_MASK BIT(9)
+#define PCS_WAKEUP_MSIP_MASK BIT(29)
#define PCS0_CTL_OFFSET 0x94
#define PCSm_CTL_OFFSET(i) ((i + 3) * 0x20 + PCS0_CTL_OFFSET)
@@ -38,6 +43,14 @@
#define WAKEUP_CMD 0x8
#define DEEP_SLEEP_CMD 0xb
+#define PCS0_STATUS_OFFSET 0x98
+#define PCSm_STATUS_OFFSET(i) ((i + 3) * 0x20 + PCS0_STATUS_OFFSET)
+#define PD_TYPE_MASK GENMASK(2, 0)
+#define PD_TYPE_SLEEP 2
+#define PD_STATUS_MASK GENMASK(7, 3)
+#define PD_STATUS_LIGHT_SLEEP 0
+#define PD_STATUS_DEEP_SLEEP 0x10
+
/* clang-format on */
void atcsmu_set_wakeup_events(u32 events, u32 hartid);
@@ -45,5 +58,7 @@ bool atcsmu_support_sleep_mode(u32 sleep_type, u32 hartid);
void atcsmu_set_command(u32 pcs_ctl, u32 hartid);
int atcsmu_set_reset_vector(u64 wakeup_addr, u32 hartid);
u32 atcsmu_get_sleep_type(u32 hartid);
+void atcsmu_write_scratch(u32 value);
+u32 atcsmu_read_scratch(void);
#endif
diff --git a/lib/utils/cache/Kconfig b/lib/utils/cache/Kconfig
index be7d57c37d24..ea815dc4ef03 100644
--- a/lib/utils/cache/Kconfig
+++ b/lib/utils/cache/Kconfig
@@ -10,6 +10,11 @@ config FDT_CACHE
if FDT_CACHE
+config FDT_CACHE_ANDES_LLCACHE
+ bool "Andes FDT last level cache driver"
+ depends on FDT_HSM_ANDES_ATCSMU
+ default n
+
config FDT_CACHE_SIFIVE_CCACHE
bool "SiFive CCACHE FDT cache driver"
default n
diff --git a/lib/utils/cache/fdt_andes_llcache.c b/lib/utils/cache/fdt_andes_llcache.c
new file mode 100644
index 000000000000..b3619c8fd83d
--- /dev/null
+++ b/lib/utils/cache/fdt_andes_llcache.c
@@ -0,0 +1,166 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Andes Technology Corporation
+ */
+
+#include <sbi/riscv_io.h>
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_heap.h>
+#include <sbi_utils/cache/fdt_cache.h>
+#include <sbi_utils/fdt/fdt_driver.h>
+#include <sbi_utils/hsm/fdt_hsm_andes_atcsmu.h>
+
+#define LLCACHE_REG_CFG_OFFSET 0x0
+#define LLCACHE_REG_CTRL_OFFSET 0x8
+#define LLCACHE_REG_CCTL_CMD_OFFSET 0x40
+#define LLCACHE_REG_CCTL_STATUS_OFFSET 0x80
+
+#define LLCACHE_REG_CFG_MAP_MASK BIT(20)
+#define LLCACHE_REG_CTRL_EN_MASK BIT(0)
+#define LLCACHE_REG_CTRL_INIT_MASK BIT(14)
+#define LLCACHE_REG_CCTL_STATUS_MASK GENMASK(3, 0)
+
+#define LLCACHE_WBINVAL_ALL 0x12
+
+struct andes_llcache {
+ struct cache_device dev;
+ void *base;
+ uint32_t cmd_stride;
+ uint32_t status_stride;
+ uint32_t status_core_stride;
+};
+
+#define to_llcache(_dev) container_of(_dev, struct andes_llcache, dev)
+
+static bool andes_llcache_init_done(struct andes_llcache *llcache)
+{
+ uint32_t llcache_ctrl;
+ void *ctrl_addr = (char *)llcache->base + LLCACHE_REG_CTRL_OFFSET;
+
+ llcache_ctrl = readl_relaxed(ctrl_addr);
+ return !EXTRACT_FIELD(llcache_ctrl, LLCACHE_REG_CTRL_INIT_MASK);
+}
+
+static bool andes_llcache_cctl_done(struct andes_llcache *llcache, uint32_t hartid)
+{
+ uint32_t llcache_cctl_status;
+ void *cctl_status_addr = (char *)llcache->base + LLCACHE_REG_CCTL_STATUS_OFFSET +
+ hartid * llcache->status_stride;
+
+ llcache_cctl_status = readl_relaxed(cctl_status_addr);
+ return !EXTRACT_FIELD(llcache_cctl_status,
+ LLCACHE_REG_CCTL_STATUS_MASK <<
+ hartid * llcache->status_core_stride);
+}
+
+static int andes_llcache_flush_all(struct cache_device *dev)
+{
+ uint32_t hartid = current_hartid();
+ struct andes_llcache *llcache = to_llcache(dev);
+ void *cctl_cmd_addr = (char *)llcache->base + LLCACHE_REG_CCTL_CMD_OFFSET +
+ hartid * llcache->cmd_stride;
+
+ /*
+ * Each command register corresponds to one CPU core, so each CPU core
+ * should only use its command registers to do the cache operation.
+ */
+ writel(LLCACHE_WBINVAL_ALL, cctl_cmd_addr);
+
+ /* Wait for the command completion */
+ while (!andes_llcache_cctl_done(llcache, hartid))
+ ;
+
+ return 0;
+}
+
+static int andes_llcache_enable(struct cache_device *dev, bool enable)
+{
+ struct andes_llcache *llcache = to_llcache(dev);
+ u32 llcache_ctrl;
+ void *ctrl_addr = (char *)llcache->base + LLCACHE_REG_CTRL_OFFSET;
+
+ /*
+ * To properly enable the last level cache to cache both instructions
+ * and data, apply the following sequence:
+ *
+ * - Write the control register with the desired value, except the
+ * CEN field should be set to zero. Thus, store the control register
+ * value with the CEN field being 0 when disabling the last level
+ * cache.
+ * - Write the control register again using the same value of step 1
+ * with the CEN field being 1.
+ */
+ if (enable) {
+ llcache_ctrl = atcsmu_read_scratch();
+ writel(llcache_ctrl, ctrl_addr);
+ writel(llcache_ctrl | LLCACHE_REG_CTRL_EN_MASK, ctrl_addr);
+ } else {
+ llcache_ctrl = readl(ctrl_addr);
+ atcsmu_write_scratch(llcache_ctrl & ~LLCACHE_REG_CTRL_EN_MASK);
+ writel(llcache_ctrl & ~LLCACHE_REG_CTRL_EN_MASK, ctrl_addr);
+ }
+
+ llcache_ctrl = readl(ctrl_addr);
+ return enable == EXTRACT_FIELD(llcache_ctrl, LLCACHE_REG_CTRL_EN_MASK);
+}
+
+static struct cache_ops andes_llcache_ops = {
+ .cache_flush_all = andes_llcache_flush_all,
+ .cache_enable = andes_llcache_enable,
+};
+
+static int andes_llcache_probe(const void *fdt, int nodeoff, const struct fdt_match *match)
+{
+ int rc;
+ u64 llcache_base = 0;
+ 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)
+ return SBI_ENODEV;
+
+ llcache = sbi_zalloc(sizeof(*llcache));
+ if (!llcache)
+ return SBI_ENOMEM;
+
+ dev = &llcache->dev;
+ dev->ops = &andes_llcache_ops;
+ rc = fdt_cache_add(fdt, nodeoff, dev);
+ if (rc) {
+ sbi_free(llcache);
+ return rc;
+ }
+
+ llcache->base = (void *)llcache_base;
+ llcache_cfg = readl_relaxed((char *)llcache->base + LLCACHE_REG_CFG_OFFSET);
+
+ /* Configurations for V1/V0 memory map */
+ if (EXTRACT_FIELD(llcache_cfg, LLCACHE_REG_CFG_MAP_MASK)) {
+ llcache->cmd_stride = 0x1000;
+ llcache->status_stride = 0x1000;
+ llcache->status_core_stride = 0;
+ } else {
+ llcache->cmd_stride = 0x10;
+ llcache->status_stride = 0x0;
+ llcache->status_core_stride = 4;
+ }
+
+ /* Wait for the hardware initialization done */
+ while (!andes_llcache_init_done(llcache))
+ ;
+
+ return SBI_OK;
+}
+
+static const struct fdt_match andes_llcache_match[] = {
+ { .compatible = "andestech,llcache" },
+ {},
+};
+
+const struct fdt_driver fdt_andes_llcache = {
+ .match_table = andes_llcache_match,
+ .init = andes_llcache_probe,
+};
diff --git a/lib/utils/cache/objects.mk b/lib/utils/cache/objects.mk
index aa76adc2e3bc..6c9bce84903e 100644
--- a/lib/utils/cache/objects.mk
+++ b/lib/utils/cache/objects.mk
@@ -8,6 +8,9 @@ libsbiutils-objs-$(CONFIG_FDT_CACHE) += cache/fdt_cache.o
libsbiutils-objs-$(CONFIG_FDT_CACHE) += cache/fdt_cache_drivers.carray.o
libsbiutils-objs-$(CONFIG_FDT_CACHE) += cache/fdt_cmo_helper.o
+carray-fdt_cache_drivers-$(CONFIG_FDT_CACHE_ANDES_LLCACHE) += fdt_andes_llcache
+libsbiutils-objs-$(CONFIG_FDT_CACHE_ANDES_LLCACHE) += cache/fdt_andes_llcache.o
+
carray-fdt_cache_drivers-$(CONFIG_FDT_CACHE_SIFIVE_CCACHE) += fdt_sifive_ccache
libsbiutils-objs-$(CONFIG_FDT_CACHE_SIFIVE_CCACHE) += cache/fdt_sifive_ccache.o
diff --git a/lib/utils/hsm/fdt_hsm_andes_atcsmu.c b/lib/utils/hsm/fdt_hsm_andes_atcsmu.c
index 3fe931aa1816..32f6fef808c6 100644
--- a/lib/utils/hsm/fdt_hsm_andes_atcsmu.c
+++ b/lib/utils/hsm/fdt_hsm_andes_atcsmu.c
@@ -84,6 +84,16 @@ u32 atcsmu_get_sleep_type(u32 hartid)
return readl_relaxed((char *)atcsmu_base + PCSm_SCRATCH_OFFSET(hartid));
}
+void atcsmu_write_scratch(u32 value)
+{
+ writel_relaxed(value, (char *)atcsmu_base + SCRATCH_PAD_OFFSET);
+}
+
+u32 atcsmu_read_scratch(void)
+{
+ return readl_relaxed((char *)atcsmu_base + SCRATCH_PAD_OFFSET);
+}
+
static int ae350_hart_start(u32 hartid, ulong saddr)
{
u32 hartindex = sbi_hartid_to_hartindex(hartid);
diff --git a/platform/generic/configs/defconfig b/platform/generic/configs/defconfig
index d85da930edbc..4b7615d6c51b 100644
--- a/platform/generic/configs/defconfig
+++ b/platform/generic/configs/defconfig
@@ -13,6 +13,7 @@ CONFIG_PLATFORM_THEAD=y
CONFIG_PLATFORM_MIPS_P8700=y
CONFIG_PLATFORM_SPACEMIT_K1=y
CONFIG_FDT_CACHE=y
+CONFIG_FDT_CACHE_ANDES_LLCACHE=y
CONFIG_FDT_CACHE_SIFIVE_CCACHE=y
CONFIG_FDT_CACHE_SIFIVE_EC=y
CONFIG_FDT_CACHE_SIFIVE_PL2=y
--
2.34.1
--
opensbi mailing list
opensbi@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/opensbi
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 5/5] lib: utils/suspend: add Andes ATCSMU suspend driver
2025-12-29 7:19 [PATCH 0/5] Add hart state management and system suspend support for AE350 Ben Zong-You Xie
` (3 preceding siblings ...)
2025-12-29 7:19 ` [PATCH 4/5] lib: utils/cache: add Andes last level cache controller Ben Zong-You Xie
@ 2025-12-29 7:19 ` Ben Zong-You Xie
2026-02-11 15:59 ` Andrew Jones
2026-02-11 6:24 ` [PATCH 0/5] Add hart state management and system suspend support for AE350 Anup Patel
5 siblings, 1 reply; 9+ messages in thread
From: Ben Zong-You Xie @ 2025-12-29 7:19 UTC (permalink / raw)
To: opensbi; +Cc: Leo Yu-Chi Liang
Implement a system-wide suspend driver for the Andes AE350 platform.
This driver supports Andes-specific deep sleep (suspend to RAM) and
light sleep (suspend to standby) functionalities via the ATCSMU.
The major differences between deep sleep and light sleep are:
- Power Domain and Resume Path: Deep sleep powers down the core domain.
Consequently, harts waking from deep sleep resume from the reset
vector. Light sleep utilizes clock gating to the core domain; harts
maintain state and resume execution at the instruction immediately
following the WFI instruction.
- Primary Hart Wakeup: In both modes, the primary hart is woken by
UART or RTC alarm interrupts. In deep sleep, the primary hart is
additionally responsible for re-enabling the Last Level Cache (LLC)
and restoring Andes-specific CSRs.
- Secondary Hart Wakeup: In light sleep, secondary harts are woken
by an IPI sent from the primary hart. In deep sleep, they are
woken by an ATCSMU hardware wake-up command. Furthermore,
secondary harts must restore Andes-specific CSRs when returning
from deep sleep.
Signed-off-by: Ben Zong-You Xie <ben717@andestech.com>
Signed-off-by: Leo Yu-Chi Liang <ycliang@andestech.com>
---
include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h | 1 +
lib/utils/hsm/fdt_hsm_andes_atcsmu.c | 49 ++++++--
lib/utils/suspend/Kconfig | 5 +
lib/utils/suspend/fdt_suspend_andes_atcsmu.c | 119 +++++++++++++++++++
lib/utils/suspend/objects.mk | 3 +
platform/generic/andes/ae350.c | 10 +-
platform/generic/configs/defconfig | 1 +
7 files changed, 178 insertions(+), 10 deletions(-)
create mode 100644 lib/utils/suspend/fdt_suspend_andes_atcsmu.c
diff --git a/include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h b/include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h
index ef851b7501b5..5e71fab7c69e 100644
--- a/include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h
+++ b/include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h
@@ -60,5 +60,6 @@ int atcsmu_set_reset_vector(u64 wakeup_addr, u32 hartid);
u32 atcsmu_get_sleep_type(u32 hartid);
void atcsmu_write_scratch(u32 value);
u32 atcsmu_read_scratch(void);
+bool atcsmu_pcs_is_sleep(u32 hartid, bool deep_sleep);
#endif
diff --git a/lib/utils/hsm/fdt_hsm_andes_atcsmu.c b/lib/utils/hsm/fdt_hsm_andes_atcsmu.c
index 32f6fef808c6..0874d365cb34 100644
--- a/lib/utils/hsm/fdt_hsm_andes_atcsmu.c
+++ b/lib/utils/hsm/fdt_hsm_andes_atcsmu.c
@@ -94,16 +94,38 @@ u32 atcsmu_read_scratch(void)
return readl_relaxed((char *)atcsmu_base + SCRATCH_PAD_OFFSET);
}
+bool atcsmu_pcs_is_sleep(u32 hartid, bool deep_sleep)
+{
+ 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;
+
+ 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;
+ }
+
+ return true;
+}
+
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))
+ 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);
@@ -130,16 +152,27 @@ static int ae350_hart_stop(void)
/* Prevent the core leaving the WFI mode unexpectedly */
csr_write(CSR_MIE, 0);
- atcsmu_set_wakeup_events(0x0, hartid);
- atcsmu_set_command(DEEP_SLEEP_CMD, hartid);
- rc = atcsmu_set_reset_vector((u64)ae350_enable_coherency_warmboot, hartid);
- if (rc)
- return SBI_EFAIL;
+ if (sleep_type == SBI_SUSP_AE350_LIGHT_SLEEP) {
+ csr_write(CSR_MIE, MIP_MSIP);
+ atcsmu_set_wakeup_events(PCS_WAKEUP_MSIP_MASK, hartid);
+ 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);
+ rc = atcsmu_set_reset_vector((u64)ae350_enable_coherency_warmboot, hartid);
+ if (rc)
+ return SBI_EFAIL;
+
+ ae350_non_ret_save(sbi_scratch_thishart_ptr());
+ }
- ae350_non_ret_save(sbi_scratch_thishart_ptr());
ae350_disable_coherency();
wfi();
- return 0;
+
+ /* Light sleep resumes here */
+ ae350_enable_coherency();
+
+ return SBI_ENOTSUPP;
}
static const struct sbi_hsm_device hsm_andes_atcsmu = {
diff --git a/lib/utils/suspend/Kconfig b/lib/utils/suspend/Kconfig
index 6c09d95253d2..8d05aff67ace 100644
--- a/lib/utils/suspend/Kconfig
+++ b/lib/utils/suspend/Kconfig
@@ -9,6 +9,11 @@ config FDT_SUSPEND
if FDT_SUSPEND
+config FDT_SUSPEND_ANDES_ATCSMU
+ bool "FDT Andes ATCSMU suspend driver"
+ depends on FDT_HSM_ANDES_ATCSMU && FDT_CACHE_ANDES_LLCACHE
+ default n
+
config FDT_SUSPEND_RPMI
bool "FDT RPMI suspend driver"
depends on FDT_MAILBOX && RPMI_MAILBOX
diff --git a/lib/utils/suspend/fdt_suspend_andes_atcsmu.c b/lib/utils/suspend/fdt_suspend_andes_atcsmu.c
new file mode 100644
index 000000000000..05e67e47eebe
--- /dev/null
+++ b/lib/utils/suspend/fdt_suspend_andes_atcsmu.c
@@ -0,0 +1,119 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Andes Technology Corporation
+ */
+
+#include <andes/andes.h>
+#include <sbi/riscv_asm.h>
+#include <sbi/sbi_console.h>
+#include <sbi/sbi_domain.h>
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_ecall_interface.h>
+#include <sbi/sbi_hart.h>
+#include <sbi/sbi_system.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)
+{
+ const struct sbi_domain *dom = &root;
+ unsigned long i;
+ u32 target;
+
+ /* Ensure 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;
+ }
+
+ return SBI_OK;
+}
+
+static int ae350_system_suspend_check(u32 sleep_type)
+{
+ return (sleep_type == SBI_SUSP_SLEEP_TYPE_SUSPEND ||
+ sleep_type == SBI_SUSP_AE350_LIGHT_SLEEP) ? SBI_OK : SBI_EINVAL;
+}
+
+static int ae350_system_suspend(u32 sleep_type, unsigned long addr)
+{
+ u32 hartid = current_hartid();
+ int rc;
+
+ /* Prevent the core leaving the WFI mode unexpectedly */
+ 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);
+ 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);
+ if (rc)
+ return rc;
+
+ atcsmu_set_command(LIGHT_SLEEP_CMD, hartid);
+ } else if (sleep_type == SBI_SUSP_SLEEP_TYPE_SUSPEND) {
+ rc = check_secondary_harts_sleep(hartid, true);
+ if (rc)
+ return rc;
+
+ atcsmu_set_command(DEEP_SLEEP_CMD, hartid);
+ rc = atcsmu_set_reset_vector((u64)ae350_enable_coherency_warmboot, hartid);
+ if (rc)
+ return rc;
+
+ ae350_non_ret_save(sbi_scratch_thishart_ptr());
+ fdt_cmo_llc_enable(false);
+ rc = fdt_cmo_llc_flush_all();
+ if (rc)
+ return rc;
+ }
+
+ ae350_disable_coherency();
+ wfi();
+
+ /* Light sleep resumes here */
+ ae350_enable_coherency();
+
+ return SBI_OK;
+}
+
+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)
+ fdt_cmo_llc_enable(true);
+}
+
+static struct sbi_system_suspend_device suspend_andes_atcsmu = {
+ .name = "andes_atcsmu",
+ .system_suspend_check = ae350_system_suspend_check,
+ .system_suspend = ae350_system_suspend,
+ .system_resume = ae350_system_resume,
+};
+
+static int suspend_andes_atcsmu_probe(const void *fdt, int nodeoff, const struct fdt_match *match)
+{
+ sbi_system_suspend_set_device(&suspend_andes_atcsmu);
+ return 0;
+}
+
+static const struct fdt_match suspend_andes_atcsmu_match[] = {
+ { .compatible = "andestech,atcsmu-sys" },
+ { },
+};
+
+const struct fdt_driver fdt_suspend_andes_atcsmu = {
+ .match_table = suspend_andes_atcsmu_match,
+ .init = suspend_andes_atcsmu_probe,
+};
diff --git a/lib/utils/suspend/objects.mk b/lib/utils/suspend/objects.mk
index 1fb29b5e7941..fb1ad9ae34c7 100644
--- a/lib/utils/suspend/objects.mk
+++ b/lib/utils/suspend/objects.mk
@@ -7,6 +7,9 @@
# Anup Patel <apatel@ventanamicro.com>
#
+carray-fdt_early_drivers-$(CONFIG_FDT_SUSPEND_ANDES_ATCSMU) += fdt_suspend_andes_atcsmu
+libsbiutils-objs-$(CONFIG_FDT_SUSPEND_ANDES_ATCSMU) += suspend/fdt_suspend_andes_atcsmu.o
+
carray-fdt_early_drivers-$(CONFIG_FDT_SUSPEND_RPMI) += fdt_suspend_rpmi
libsbiutils-objs-$(CONFIG_FDT_SUSPEND_RPMI) += suspend/fdt_suspend_rpmi.o
diff --git a/platform/generic/andes/ae350.c b/platform/generic/andes/ae350.c
index 9bd1554394b7..4cf7e26def54 100644
--- a/platform/generic/andes/ae350.c
+++ b/platform/generic/andes/ae350.c
@@ -8,9 +8,12 @@
#include <andes/andes_pmu.h>
#include <andes/andes_sbi.h>
#include <platform_override.h>
+#include <sbi/sbi_ecall_interface.h>
+#include <sbi/riscv_asm.h>
#include <sbi/sbi_init.h>
#include <sbi/sbi_scratch.h>
#include <sbi_utils/fdt/fdt_helper.h>
+#include <sbi_utils/hsm/fdt_hsm_andes_atcsmu.h>
static unsigned long andes_hart_data_offset;
extern void _start_warm(void);
@@ -59,14 +62,17 @@ void ae350_enable_coherency_warmboot(void)
static int ae350_early_init(bool cold_boot)
{
+ u32 hartid = current_hartid();
+ u32 sleep_type = atcsmu_get_sleep_type(hartid);
+
if (cold_boot) {
andes_hart_data_offset = sbi_scratch_alloc_offset(sizeof(struct andes_hart_data));
if (!andes_hart_data_offset)
return SBI_ENOMEM;
}
- /* Don't restore Andes CSRs during boot */
- if (sbi_init_count(current_hartindex()))
+ /* Don't restore Andes CSRs during boot or wake up from light sleep */
+ if (sbi_init_count(current_hartindex()) && sleep_type == SBI_SUSP_SLEEP_TYPE_SUSPEND)
ae350_non_ret_restore(sbi_scratch_thishart_ptr());
return generic_early_init(cold_boot);
diff --git a/platform/generic/configs/defconfig b/platform/generic/configs/defconfig
index 4b7615d6c51b..19a73a247765 100644
--- a/platform/generic/configs/defconfig
+++ b/platform/generic/configs/defconfig
@@ -62,6 +62,7 @@ CONFIG_FDT_SERIAL_UART8250=y
CONFIG_FDT_SERIAL_XILINX_UARTLITE=y
CONFIG_SERIAL_SEMIHOSTING=y
CONFIG_FDT_SUSPEND=y
+CONFIG_FDT_SUSPEND_ANDES_ATCSMU=y
CONFIG_FDT_SUSPEND_RPMI=y
CONFIG_FDT_SUSPEND_SIFIVE_SMC0=y
CONFIG_FDT_TIMER=y
--
2.34.1
--
opensbi mailing list
opensbi@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/opensbi
^ permalink raw reply related [flat|nested] 9+ messages in thread