[PATCH v2 5/5] platform: generic: eswin: add EIC7700

Bo Gan
2025-11-17T05:50:45Z

Initial platform support for ESWIN Computing EIC7700 based on public SoC
datasheet[1] and tested on HiFive Premier P550. Vendor U-boot/Linux boots
fine, and I've tested Geekbench 6.5.0 Preview and got scores on par with
the vendor OpenSBI. System shutdown is not implemented due to the lack of
public doc regarding the UART communication protocol between the SoC and
the onboard BMC, which controls ATX power on HiFive P550. [2]

The files and functions are intentionally named as eic770x in many places
for future enhancements to support the 2 die version of the same SoC,
namely EIC7702, seen on DC-ROMA AI PC FML13V03 [3]. This patch set only
deals with the single die version, and the assumption is we can be either
die with id=0 or id=1, but there's only a single die in the system, or we
are only using a single die out of 2. However, the way the SoC handles 2-
die greatly affects how we configure it in a 1-die setup. EIC770X address
map has die 0/1 memory regions interleaved (see comments in eic770x.c).
If only 1 die is connected or active, it creates holes in the address map
for those regions corresponding to the remote die. Although not mentioned
in the datasheet, experiment shows speculative or HW prefetch can trigger
bus errors when touching those "holes". We need to use PMP to block the
access to those "holes", necessitating very tricky PMP configuration.

To make matters worse, EIC770X doesn't have cache coherent DMA, and due
to the fact that the P550 core lacks Svpbmt, the SoC maps main memory
twice as different regions in address map, so it can bypass cache and
fetch data directly from memory. In address space, we have two memory
regions, one as cached, the other as uncached. Thus, we also need an
extra PMP entry to protect OpenSBI blob from the uncached window. To do
this, we require FIRMWARE_PACKED_RXRW, otherwise, we'll run out of PMP
entries. Currently we barely made it to cover everything with the total
8 avilable PMP:

For root domain harts:

PMP[0] NAPOT: Protect OpenSBI in cached memory window
PMP[1] NAPOT: Protect OpenSBI in uncached memory window
PMP[2] NAPOT: Protect CLINT
PMP[3] NAPOT: Enable cached memory (die 1 only)
PMP[4] NAPOT: Block P550 internal on remote die
PMP[5-6] TOR: Block holes in memory port on remote die
PMP[7] NAPOT: Allow everything

EIC770X also have several feature disable/enable CSRs accessible in M
mode. By default many core features such as speculation and HW prefetch
are disabled, and M mode software is responsible of enabling. Hence,
introduce 4 new build time tunable parameters to Kconfig, which reflects
the values get updated to those CSRs:
- ESWIN_EIC770X_FEAT0_CFG
- ESWIN_EIC770X_FEAT1_CFG
- ESWIN_EIC770X_L1_HWPF_CFG
- ESWIN_EIC770X_L2_HWPF_CFG

The default values are somewhat optimal for generic workloads. They are
dumped when running SiFive's vendor OpenSBI, and in addition, with my
own tuning to address the perf regression reported by drmpeg [4]

To build the firmware+u-boot blob, Use the following, and docs [5] for
testing it with UART boot without flashing:

make PLATFORM_DEFCONFIG=eic770x_defconfig \
FW_TEXT_START=0x80000000 \
FW_PAYLOAD_OFFSET=0x200000 \
FW_PAYLOAD_PATH=u-boot-nodtb.bin \
FW_PAYLOAD_FDT_ADDR=0xf8000000 \
FW_FDT_PATH=u-boot.dtb

[1] https://github.com/eswincomputing/EIC7700X-SoC-Technical-Reference-Manual
[2] https://www.sifive.com/boards/hifive-premier-p550#documentation
[3] https://github.com/geerlingguy/sbc-reviews/issues/82
[4] https://forums.sifive.com/t/low-1-core-stream-bandwidth/7274/15
[5] https://github.com/ganboing/EIC770x-Docs/blob/main/p550/bootchain/UART-Boot.md

Signed-off-by: Bo Gan <ganboing@gmail.com>
---
platform/generic/Kconfig | 6 +
platform/generic/configs/eic770x_defconfig | 28 ++
platform/generic/eswin/Kconfig | 29 ++
platform/generic/eswin/eic770x.c | 361 +++++++++++++++++++++
platform/generic/eswin/objects.mk | 11 +
platform/generic/include/eswin/eic770x.h | 66 ++++
6 files changed, 501 insertions(+)
create mode 100644 platform/generic/configs/eic770x_defconfig
create mode 100644 platform/generic/eswin/Kconfig
create mode 100644 platform/generic/eswin/eic770x.c
create mode 100644 platform/generic/eswin/objects.mk
create mode 100644 platform/generic/include/eswin/eic770x.h

diff --git a/platform/generic/Kconfig b/platform/generic/Kconfig
index aedc59a5..adffd5c9 100644
--- a/platform/generic/Kconfig
+++ b/platform/generic/Kconfig
@@ -88,7 +88,13 @@ config PLATFORM_SPACEMIT_K1
select FDT_HSM_SPACEMIT
default n

+config PLATFORM_ESWIN_EIC770X
+ bool "ESWIN EIC770X support"
+ depends on FIRMWARE_PACKED_RXRW
+ default n
+
source "$(OPENSBI_SRC_DIR)/platform/generic/andes/Kconfig"
+source "$(OPENSBI_SRC_DIR)/platform/generic/eswin/Kconfig"
source "$(OPENSBI_SRC_DIR)/platform/generic/thead/Kconfig"

endif
diff --git a/platform/generic/configs/eic770x_defconfig b/platform/generic/configs/eic770x_defconfig
new file mode 100644
index 00000000..f33778c9
--- /dev/null
+++ b/platform/generic/configs/eic770x_defconfig
@@ -0,0 +1,28 @@
+CONFIG_PLATFORM_ESWIN_EIC770X=y
+CONFIG_FIRMWARE_PACKED_RXRW=y
+CONFIG_FDT_CACHE=y
+CONFIG_FDT_CACHE_SIFIVE_CCACHE=y
+CONFIG_FDT_CPPC=y
+CONFIG_FDT_CPPC_RPMI=y
+CONFIG_FDT_HSM=y
+CONFIG_FDT_HSM_RPMI=y
+CONFIG_FDT_IPI=y
+CONFIG_FDT_IPI_MSWI=y
+CONFIG_FDT_IRQCHIP=y
+CONFIG_FDT_IRQCHIP_PLIC=y
+CONFIG_FDT_MAILBOX=y
+CONFIG_RPMI_MAILBOX=y
+CONFIG_FDT_MAILBOX_RPMI_SHMEM=y
+CONFIG_FDT_SERIAL=y
+CONFIG_FDT_SERIAL_UART8250=y
+CONFIG_FDT_SUSPEND=y
+CONFIG_FDT_SUSPEND_RPMI=y
+CONFIG_FDT_TIMER=y
+CONFIG_FDT_TIMER_MTIMER=y
+CONFIG_FDT_MPXY=y
+CONFIG_FDT_MPXY_RPMI_MBOX=y
+CONFIG_FDT_MPXY_RPMI_CLOCK=y
+CONFIG_FDT_MPXY_RPMI_VOLTAGE=y
+CONFIG_FDT_MPXY_RPMI_DEVICE_POWER=y
+CONFIG_FDT_MPXY_RPMI_PERFORMANCE=y
+CONFIG_FDT_MPXY_RPMI_SYSMSI=y
diff --git a/platform/generic/eswin/Kconfig b/platform/generic/eswin/Kconfig
new file mode 100644
index 00000000..84d0f43a
--- /dev/null
+++ b/platform/generic/eswin/Kconfig
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+config ESWIN_EIC770X_FEAT0_CFG
+ int "ESWIN EIC7700X Feature Disable 0 CSR Configuration"
+ default 0x4000
+ help
+ CSR Value to initialize EIC770X_FEAT0 (0x7c1) with.
+ Refer to EIC770X SoC TRM for recommendations.
+
+config ESWIN_EIC770X_FEAT1_CFG
+ int "ESWIN EIC7700X Feature Disable 1 CSR Configuration"
+ default 0x80
+ help
+ CSR Value to initialize EIC770X_FEAT1 (0x7c2) with.
+ Refer to EIC770X SoC TRM for recommendations.
+
+config ESWIN_EIC770X_L1_HWPF_CFG
+ int "ESWIN EIC7700X L1 HW Prefetcher CSR Configuration"
+ default 0x1005c1be649
+ help
+ CSR Value to initialize EIC770X_L1_HWPF (0x7c3) with.
+ Refer to EIC770X SoC TRM for recommendations.
+
+config ESWIN_EIC770X_L2_HWPF_CFG
+ int "ESWIN EIC7700X L2 HW Prefetcher CSR Configuration"
+ default 0x929f
+ help
+ CSR Value to initialize EIC770X_L2_HWPF (0x7c4) with.
+ Refer to EIC770X SoC TRM for recommendations.
diff --git a/platform/generic/eswin/eic770x.c b/platform/generic/eswin/eic770x.c
new file mode 100644
index 00000000..be8894ad
--- /dev/null
+++ b/platform/generic/eswin/eic770x.c
@@ -0,0 +1,361 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Authors:
+ * Bo Gan <ganboing@gmail.com>
+ *
+ */
+
+#include <platform_override.h>
+#include <sbi/riscv_io.h>
+#include <sbi/sbi_console.h>
+#include <sbi/sbi_system.h>
+#include <sbi/sbi_math.h>
+#include <eswin/eic770x.h>
+
+static int eic770x_system_reset_check(u32 type, u32 reason)
+{
+ switch (type) {
+ case SBI_SRST_RESET_TYPE_COLD_REBOOT:
+ case SBI_SRST_RESET_TYPE_WARM_REBOOT:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static void eic770x_system_reset(u32 type, u32 reason)
+{
+ switch (type) {
+ case SBI_SRST_RESET_TYPE_COLD_REBOOT:
+ case SBI_SRST_RESET_TYPE_WARM_REBOOT:
+ writel(EIC770X_SYSCRG_RST_VAL, (void *)EIC770X_SYSCRG_RST);
+ }
+
+ sbi_printf("%s: Unable to reset system\n", __func__);
+ sbi_hart_hang();
+}
+
+static struct sbi_system_reset_device eic770x_reset = {
+ .name = "eic770x_reset",
+ .system_reset_check = eic770x_system_reset_check,
+ .system_reset = eic770x_system_reset
+};
+
+static int eswin_eic7700_early_init(bool cold_boot)
+{
+ if (!cold_boot)
+ return generic_early_init(cold_boot);
+
+ /* Enable bus blocker */
+ writel(1, (void*)EIC770X_TL64D2D_OUT);
+ writel(1, (void*)EIC770X_TL256D2D_OUT);
+ writel(1, (void*)EIC770X_TL256D2D_IN);
+ asm volatile ("fence o, rw");
+
+ return generic_early_init(cold_boot);
+}
+
+/**
+ * EIC7700 special arrangement of PMP entries:
+ *
+ * We have to use extra PMPs to block Memory Port regions that doesn't
+ * belong to the current hart's die in order to prevent speculative
+ * accesses or HW prefetcher (perhaps?) from generating bus error:
+ *
+ * bus error of cause event: 9, accrued: 0x220,
+ * physical address: 0x24ffffffa0
+ *
+ * The extra PMPs need LOCK bit to be set in order to be effective
+ * in M mode as well.
+ *
+ * We also have to to use 1 extra PMP to protect OpenSBI in uncached
+ * memory. EIC770X maps main memory (DRAM) twice -- one in memory
+ * port (cached), the other in system port (uncached). P550 doesn't
+ * support Svpbmt, so EIC770X use the uncached window to handle DMA
+ * that are cache incoherent -- pretty much all peripherals
+ *
+ * EIC770X memory port map:
+ * P550 Internal
+ * ├─ 0x00_0000_0000 - 0x00_2000_0000 die 0 internal
+ * └─ 0x00_2000_0000 - 0x00_4000_0000 die 1 internal
+ * System Port 0
+ * ├─ 0x00_4000_0000 - 0x00_6000_0000 die 0 low MMIO
+ * └─ 0x00_6000_0000 - 0x00_8000_0000 die 1 low MMIO
+ * Memory Port
+ * ├─ 0x00_8000_0000 - 0x10_8000_0000 die 0 memory (cached)
+ * ├─ 0x20_0000_0000 - 0x30_0000_0000 die 1 memory (cached)
+ * └─ 0x40_0000_0000 - 0x60_0000_0000 interleaved memory (cached)
+ * System Port 1
+ * ├─ 0x80_0000_0000 - 0xa0_0000_0000 die 0 high MMIO
+ * ├─ 0xa0_0000_0000 - 0xc0_0000_0000 die 1 high MMIO
+ * ├─ 0xc0_0000_0000 - 0xd0_0000_0000 die 0 memory (uncached)
+ * ├─ 0xe0_0000_0000 - 0xf0_0000_0000 die 1 memory (uncached)
+ * ├─ 0x100_0000_0000 - 0x120_0000_0000 interleaved memory (uncached)
+ * └─ ...
+ *
+ * Refer to comment below for PMP entries setup in different scenarios:
+ */
+static int eswin_eic7700_final_init(bool cold_boot)
+{
+ struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
+ struct sbi_domain_memregion fw_memreg;
+ int rc;
+
+ if (cold_boot)
+ sbi_system_reset_add_device(&eic770x_reset);
+
+ /**
+ * Do generic_final_init stuff first, because it touchs FDT.
+ * The PMPs blocking memory regions have LOCK bit set, so
+ * before pmp_configure, we can't access memory outside of
+ * [fw_start, fw_start + fw_size). The FDT could very well
+ * reside outside of firmware region.
+ */
+ rc = generic_final_init(cold_boot);
+ if (rc)
+ return rc;
+
+ sbi_domain_memregion_init(scratch->fw_start, scratch->fw_size,
+ (SBI_DOMAIN_MEMREGION_M_READABLE |
+ SBI_DOMAIN_MEMREGION_M_WRITABLE |
+ SBI_DOMAIN_MEMREGION_M_EXECUTABLE |
+ SBI_DOMAIN_MEMREGION_FW), &fw_memreg);
+
+ pmp_set(0, 0, fw_memreg.base, fw_memreg.order);
+ pmp_set(1, 0, EIC770X_TO_UNCACHED(fw_memreg.base), fw_memreg.order);
+
+#define PMP_FREE_START 2
+#define PMP_FREE_COUNT 2
+#define PMP_LAST 7
+ if (current_hart_die()) {
+ /**
+ * Die 1:
+ *
+ * PMP[0] NAPOT: Protect OpenSBI in cached memory
+ * PMP[1] NAPOT: Protect OpenSBI in uncached memory
+ * PMP[2-3] : Free
+ * PMP[4] NAPOT: Disallow die 0 P550 internal
+ * PMP[5-6] TOR: Disallow memory port
+ * PMP[7] NAPOT: Match all (optional)
+ */
+ pmp_set(4, PMP_L, EIC770X_P550INT_BASE(0),
+ log2roundup(EIC770X_P550INT_SIZE));
+ __pmp_set_tor(6, PMP_L, EIC770X_MEMPORT_BASE,
+ EIC770X_MEMPORT_SIZE);
+ } else {
+ /**
+ * Die 0:
+ *
+ * PMP[0] NAPOT: Protect OpenSBI in cached memory
+ * PMP[1] NAPOT: Protect OpenSBI in uncached memory
+ * PMP[2-3] : Free
+ * PMP[4] NAPOT: Disallow die 1 P550 internal
+ * PMP[5-6] TOR: Disallow memory port except die 0 cached
+ * PMP[7] NAPOT: Match all (optional)
+ */
+ pmp_set(4, PMP_L, EIC770X_P550INT_BASE(1),
+ log2roundup(EIC770X_P550INT_SIZE));
+ __pmp_set_tor(6, PMP_L,
+ EIC770X_D0_MEM_BASE + EIC770X_D0_MEM_SIZE,
+ EIC770X_MEMPORT_SIZE - EIC770X_D0_MEM_SIZE);
+ }
+ /**
+ * These must come after the setup of PMP, as we are about to
+ * enable speculation and HW prefetcher bits
+ */
+ csr_write(EIC770X_CSR_FEAT0, CONFIG_ESWIN_EIC770X_FEAT0_CFG);
+ csr_write(EIC770X_CSR_FEAT1, CONFIG_ESWIN_EIC770X_FEAT1_CFG);
+ csr_write(EIC770X_CSR_L1_HWPF, CONFIG_ESWIN_EIC770X_L1_HWPF_CFG);
+ csr_write(EIC770X_CSR_L2_HWPF, CONFIG_ESWIN_EIC770X_L2_HWPF_CFG);
+
+ return 0;
+}
+
+/**
+ * Check if memregion A supersedes B, or vice versa. It helps to save
+ * precious PMP entries. The policy is as follows (given A/B contains
+ * each other). If A is superset of B, then B is useless (PMP A will
+ * match before B) Else, B is a strict super set of A:
+ *
+ * First normalize permissions on M/SU based on ENF_PERMISSIONS, then
+ * check if both M and SU permission are exactly the same. Only then
+ * can we say B supersedes A. We make one relaxation that we treat A
+ * as having SU Executable (X) if A has SURW and B has SURWX. We
+ * assume S-mode OS does its job by enforcing with page-tables.
+ * This'll get us more savings.
+ *
+ * Returns: >0: A supersedes B, <0: B supersedes A, ==0: neither
+ */
+static int eswin_eic770x_memreg_superseded(struct sbi_domain_memregion *a,
+ struct sbi_domain_memregion *b)
+{
+ unsigned long
+ mflags_a = (a->flags & SBI_DOMAIN_MEMREGION_ENF_PERMISSIONS) ?
+ a->flags & SBI_DOMAIN_MEMREGION_M_ACCESS_MASK :
+ SBI_DOMAIN_MEMREGION_M_RWX,
+ mflags_b = (b->flags & SBI_DOMAIN_MEMREGION_ENF_PERMISSIONS) ?
+ b->flags & SBI_DOMAIN_MEMREGION_M_ACCESS_MASK :
+ SBI_DOMAIN_MEMREGION_M_RWX,
+ suflags_a = a->flags & SBI_DOMAIN_MEMREGION_SU_ACCESS_MASK,
+ suflags_b = b->flags & SBI_DOMAIN_MEMREGION_SU_ACCESS_MASK;
+
+ if (a->order >= b->order)
+ return 1;
+
+ if (mflags_a != mflags_b)
+ return 0;
+
+ if (suflags_a == suflags_b ||
+ (suflags_a | SBI_DOMAIN_MEMREGION_SU_EXECUTABLE) == suflags_b)
+ return -1;
+
+ return 0;
+}
+
+/* Returns a bitmask of memregions superseded and skipped */
+static uint64_t eswin_eic770x_memreg_consolidate(struct sbi_domain *dom)
+{
+ struct sbi_domain_memregion *rega, *regb;
+ uint64_t skipped = 0;
+ unsigned i, j;
+ int rc;
+
+ /* Ignore all FIRMWARE regions (handled separately) */
+ sbi_domain_for_each_memregion_idx(dom, rega, i) {
+ if (SBI_DOMAIN_MEMREGION_IS_FIRMWARE(rega->flags))
+ skipped |= BIT(i);
+ }
+
+ sbi_domain_for_each_memregion_idx(dom, rega, i) {
+ if (skipped & BIT(i))
+ continue;
+
+ sbi_domain_for_each_memregion_idx(dom, regb, j) {
+ if (j <= i || (skipped & BIT(j)))
+ continue;
+
+ if (sbi_domain_memregion_disjoint(rega, regb))
+ continue;
+ /**
+ * Now A and B must fully contain each other
+ * or they are exactly the same range. Check
+ * if one can supersede the other. If not,
+ * we must break futher processing. In essense
+ * it's logically wrong to compare two regions
+ * when there's a third region in between that
+ * also covers part or all of either of them.
+ */
+ rc = eswin_eic770x_memreg_superseded(rega, regb);
+ if (!rc)
+ break;
+
+ if (rc > 0) {
+ skipped |= BIT(j);
+ continue;
+ }
+ skipped |= BIT(i);
+ break;
+ }
+ }
+
+ return skipped;
+}
+
+static int eswin_eic7700_pmp_configure(unsigned int pmp_count,
+ unsigned int pmp_log2gran,
+ unsigned long pmp_addr_max)
+{
+ struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
+ struct sbi_domain *dom = sbi_domain_thishart_ptr();
+ struct sbi_domain_memregion temp, *reg;
+ unsigned int pmp_idx = PMP_FREE_START,
+ pmp_max = pmp_idx + PMP_FREE_COUNT;
+ unsigned int i;
+ uint64_t memreg_skipped;
+
+ /* Check if some memreg can be safely ignored */
+ memreg_skipped = eswin_eic770x_memreg_consolidate(dom);
+
+ /* Special handling of match all region [0, -1] if present */
+ sbi_domain_for_each_memregion_idx(dom, reg, i) {
+ if (memreg_skipped & BIT(i))
+ continue;
+
+ if (reg->order >= __riscv_xlen) {
+ /**
+ * For die 1, when there is a match all region,
+ * such as the [0, -1] in root domain, we need
+ * to use PMP[3] with NAPOT to reenable die 1
+ * cached memory from the memory port region we
+ * blocked earlier. This is not required for die
+ * 0, as we didn't block die 0 cached memory, as
+ * it happens to be at the bottom of memory port,
+ * thus excluded.
+ */
+ if (current_hart_die())
+ pmp_max--;
+ break;
+ }
+ }
+
+ /* Actually set PMPs */
+ sbi_domain_for_each_memregion_idx(dom, reg, i) {
+ if (memreg_skipped & BIT(i))
+ continue;
+
+ if (reg->order < __riscv_xlen) {
+ if (pmp_idx >= pmp_max)
+ goto no_more_pmp;
+
+ sbi_hart_oldpmp_set(scratch, dom, reg, pmp_idx++,
+ pmp_log2gran, pmp_addr_max);
+ continue;
+ }
+
+ if (current_hart_die()) {
+ sbi_domain_memregion_init(EIC770X_D1_MEM_BASE,
+ EIC770X_D1_MEM_SIZE,
+ reg->flags, &temp);
+ sbi_hart_oldpmp_set(scratch, dom, &temp, pmp_max,
+ pmp_log2gran, pmp_addr_max);
+ }
+ sbi_hart_oldpmp_set(scratch, dom, reg, PMP_LAST,
+ pmp_log2gran, pmp_addr_max);
+ break;
+ }
+
+ return 0;
+no_more_pmp:
+ sbi_printf("%s: insufficient PMP entries\n", __func__);
+ return SBI_EFAIL;
+}
+
+static void eswin_eic7700_pmp_unconfigure(void)
+{
+ for (unsigned i = 0; i < PMP_FREE_COUNT; i++)
+ pmp_disable(i + PMP_FREE_START);
+
+ pmp_disable(PMP_LAST);
+}
+
+static int eswin_eic7700_platform_init(const void *fdt, int nodeoff,
+ const struct fdt_match *match)
+{
+ generic_platform_ops.early_init = eswin_eic7700_early_init;
+ generic_platform_ops.final_init = eswin_eic7700_final_init;
+ generic_platform_ops.pmp_configure = eswin_eic7700_pmp_configure;
+ generic_platform_ops.pmp_unconfigure = eswin_eic7700_pmp_unconfigure;
+
+ return 0;
+}
+
+static const struct fdt_match eswin_eic7700_match[] = {
+ { .compatible = "eswin,eic7700" },
+ { },
+};
+
+const struct fdt_driver eswin_eic7700 = {
+ .match_table = eswin_eic7700_match,
+ .init = eswin_eic7700_platform_init,
+};
diff --git a/platform/generic/eswin/objects.mk b/platform/generic/eswin/objects.mk
new file mode 100644
index 00000000..6942a107
--- /dev/null
+++ b/platform/generic/eswin/objects.mk
@@ -0,0 +1,11 @@
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (C) 2025 Bo Gan <ganboing@gmail.com>
+#
+
+carray-platform_override_modules-$(CONFIG_PLATFORM_ESWIN_EIC770X) += eswin_eic7700
+platform-objs-$(CONFIG_PLATFORM_ESWIN_EIC770X) += eswin/eic770x.o
+
+FW_PAYLOAD=y
+FW_PAYLOAD_OFFSET=0x200000
diff --git a/platform/generic/include/eswin/eic770x.h b/platform/generic/include/eswin/eic770x.h
new file mode 100644
index 00000000..4eb61546
--- /dev/null
+++ b/platform/generic/include/eswin/eic770x.h
@@ -0,0 +1,66 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Bo Gan <ganboing@gmail.com>
+ *
+ */
+
+#ifndef __EIC770X_H__
+#define __EIC770X_H__
+
+/* CSRs */
+#define EIC770X_CSR_BRPREDICT 0x7c0
+#define EIC770X_CSR_FEAT0 0x7c1
+#define EIC770X_CSR_FEAT1 0x7c2
+#define EIC770X_CSR_L1_HWPF 0x7c3
+#define EIC770X_CSR_L2_HWPF 0x7c4
+
+/* Hart ID to core/die conversion */
+#define CPU_CORE_BITS 2
+#define CPU_CORE_MASK ((1 << CPU_CORE_BITS) - 1)
+#define CPU_DIE_SHIFT CPU_CORE_BITS
+#define CPU_DIE_BITS 1
+#define CPU_DIE_MASK ((1 << CPU_DIE_SHIFT) - 1)
+
+#define hart_core(i) ((i) & CPU_CORE_MASK)
+#define hart_die(i) (((i) >> CPU_DIE_SHIFT) & CPU_DIE_MASK)
+#define current_hart_core() hart_core(current_hartid())
+#define current_hart_die() hart_die(current_hartid())
+
+/* P550 Internal and System Port 0 */
+#define EIC770X_P550INT_SIZE 0x20000000UL
+#define EIC770X_P550INT_BASE(d) (0UL + EIC770X_P550INT_SIZE * (d))
+#define EIC770X_P550INT_LOCAL EIC770X_P550INT_BASE(current_hart_die())
+#define EIC770X_TL64D2D_OUT (EIC770X_P550INT_LOCAL + 0x200000)
+#define EIC770X_TL256D2D_OUT (EIC770X_P550INT_LOCAL + 0x202000)
+#define EIC770X_TL256D2D_IN (EIC770X_P550INT_LOCAL + 0x204000)
+
+#define EIC770X_SYSPORT_SIZE 0x20000000UL
+#define EIC770X_SYSPORT_BASE(d) (0x40000000UL + EIC770X_SYSPORT_SIZE * (d))
+#define EIC770X_SYSPORT_LOCAL EIC770X_SYSPORT_BASE(current_hart_die())
+#define EIC770X_SYSCRG (EIC770X_SYSPORT_LOCAL + 0x11828000UL)
+#define EIC770X_SYSCRG_RST (EIC770X_SYSCRG + 0x300UL)
+#define EIC770X_SYSCRG_RST_VAL 0x1AC0FFE6UL
+
+/* Memory Ports */
+#define EIC770X_MEMPORT_BASE 0x0080000000UL // 2G
+#define EIC770X_MEMPORT_SIZE 0x7f80000000UL // +510G
+#define EIC770X_D0_MEM_BASE 0x0080000000UL // 2G
+#define EIC770X_D0_MEM_SIZE 0x0f80000000UL // +62G
+#define EIC770X_D1_MEM_BASE 0x2000000000UL // 128G
+#define EIC770X_D1_MEM_SIZE 0x1000000000UL // +64G
+#define EIC770X_CACHED_BASE (current_hart_die() ? \
+ EIC770X_D1_MEM_BASE : \
+ EIC770X_D0_MEM_BASE)
+
+/* Uncached memory mapped in System Port 1 */
+#define EIC770X_D0_UC_BASE 0xc000000000UL
+#define EIC770X_D1_UC_BASE 0xe000000000UL
+#define EIC770X_UNCACHED_BASE (current_hart_die() ? \
+ EIC770X_D1_UC_BASE : \
+ EIC770X_D0_UC_BASE)
+
+#define EIC770X_TO_UNCACHED(x) ((x) - EIC770X_CACHED_BASE + \
+ EIC770X_UNCACHED_BASE)
+
+#endif